GDPR & Data Protection Strategy β
GDPR compliance is built directly into the BusFlow platform architecture via core Privacy by Design principles.
1. Technical and Organizational Measures (TOMs) β
- Encryption: AES-256 for data at rest (Hetzner Volumes) and TLS 1.3 for all data in transit (Traefik edge).
- EU data residency: all primary application data resides on Hetzner infrastructure in Falkenstein (
fsn1). Production Postgres runs on Ubicloud Managed Postgres ineu-central-h1β the Ubicloud SLA Β§(EU residency) clause guarantees bare-metal residency within the EU and is cited here as the DPIA audit-trail reference. The cutover record lives in ADR-022. - Access Control: RBAC via Hasura limits API mutations strictly to authenticated tenants. Staff access requires SSO+2FA.
- Audit Logging: Postgres logical replication streams critical entity mutations safely into a write-once audit ledger in Hasura.
- Data Minimization: Passenger schemas store only the fields explicitly needed for ticket generation and transport liability.
2. Operator Onboarding (DPA) β
- During the B2B tenant onboarding flow, a standardized Data Processing Agreement (DPA) based on the BITKOM template is electronically signed via DocuSign integration.
- Busflow acts purely as the Data Processor (Auftragsverarbeiter); the Operator remains the Data Controller (Verantwortlicher).
3. Data Flow & Boundary Isolation β
- The Commerce Bounded Context strictly constrains PII (Passenger Details, Reseller Contacts). Other contexts refer to these entities via generic UUIDs, avoiding downstream PII leaks.
- Client-Side Filtering: The observability layer (Grafana Faro SDK) sanitizes query parameters and DOM elements to catch PII leaks prior to log transmission.
4. Data Subject Rights Workflows β
Right to Access/Portability: Tenants can invoke a "Generate GDPR Export" REST endpoint via NestJS, which aggregates passenger payload data from the Commerce schema into a standard machine-readable JSON structure.
Right to Erasure (Deletion): The system automates strict physical data scrubbing via a
pg_cronpipeline with per-entity retention windows β not a uniform 3-year wipe. Each window requires Legal/DPO sign-off and is codified in ADR-028:Entity Window Trigger column Legal basis commerce.passengers3 years last_booking_atBDSG Β§ 35 backoffice.resellers2 years last_active_atBDSG Β§ 35 commerce.invoices(PII inrecipient_snapshot)10 years issued_atGoBD Β§ 147 AO, Β§ 14b UStG Chat transcripts (Loki log streams) 14 days log rotation 30-day GDPR window communications.messages(Postgres β see below)retained (see "Separation of concerns") n/a operational data Sub-Entity Cascading (tombstone, not delete): The scrubbing pipeline cascades pseudonymization across sub-entities β Commerce
passengers(redactfirst_name,last_name,email,phone), associatedtickets(void).payments.refund_passenger_idkeeps the UUID so referential integrity is preserved; the row is "tombstoned" (PII columns wiped) not deleted. Hasura gets read-only permissions on tombstoned rows for financial reconciliation.Idempotency via dedicated column: scrubs guard on
passengers.pii_redacted_at IS NULL(a dedicatedTIMESTAMPTZ), not on a text sentinel likefirst_name <> '[REDACTED]'β which would fail against a real traveller whose legal first name happens to be[REDACTED].UTC schedules, local reporting: all
pg_cronschedules and alltenant_scrub_logs.scrubbed_atvalues are UTC. Operator-local compliance reports derive display time fromtenants.tzat render time. A DPIA auditor who reads "scrub ran at 03:00" will never need to reconcile two timezones.Online back-fill:
last_booking_atis populated by a NestJS worker (pii-backfill.worker.ts) that chunks 10 000 rows with a 1 s delay. The column staysNULLABLEforever β aNULLmeans "unknown, skip in scrub", not "ready to redact".Legal-hold override: the
backoffice.legal_holdstable (created in the same migration as the scrub functions) records active investigations. Each per-entity redaction function reads this table first; a passenger/reseller/invoice/conversation on an active hold is skipped and logged asSKIPPED_LEGAL_HOLDintenant_scrub_logs. Runbook:legal-hold-runbook.md.Scrub Audit Log: A dedicated
tenant_scrub_logstable (append-only;REVOKE UPDATE, DELETE) records the UUIDs of scrubbed records, entity type, execution timestamp, and skip reason. The log omits actual redacted data β it proves the scrub occurred without reintroducing PII.communications.messagesβ Separation of concerns: Thecommunications.messagestable is operational Postgres data, not a log stream.rendered_contentholds rendered names ("Dear Jane, your bookingβ¦"). Our position is that once the sourcecommerce.passengersrow is redacted, the rendered reference is an orphaned name fragment that can no longer re-identify anyone without the source row. The Loki 14-day TTL handles leaked PII in log streams. If Legal/DPO rejects this position, we activate a gated Stage 9 cascading text-replace (passenger β booking β conversation linkage) β not yet implemented; see the architect-loop doc for the contract.Immutability Edge Case (logs, not messages): For raw log trails (Loki/Tempo), retroactive scrubbing of append-only chunks is computationally hazardous. Instead, the system relies on a strict 14-day retention TTL to naturally age out any inadvertently logged PII, well within the legally compliant 30-day window. This is managed by the Loki compactor β not by MinIO bucket-lifecycle rules, which cause "chunk not found" errors at query time.
5. Breach Protocol & Secrets β
- Infrastructure Secrets: All core infrastructure credentials utilize Swarm Docker Secrets (
/run/secrets/), with values automatically injected via GitHub Actions CI/CD to eliminate manual rotation. - Tenant Credentials: Sensitive tenant API configurations are protected at the database level using application-scale encryption (
pgsodium) and securely masked before API exposure. - Continuous SAST checks limit inadvertent data leakage at build-time.
- Production exposure events immediately trigger the
#security-alertsescalation protocol to meet the 72-hour notification mandate.