Data Protection

Audience: anyone who designs, deploys, or operates this system. Read after 00-overview.md and 01-architecture-summary.md.

Purpose: to ensure that no party — including Google, CNTXT, an attacker who compromises a single layer, or a buggy service inside our own walls — can read customer data they shouldn’t see. The threat model is broad on purpose.

These guardrails are M01 deliverables. They are wired in before anything sensitive is stored.


1. Threat model

We design against:

  • A compromised Google / CNTXT employee or process with read access to the underlying storage. We hold our own encryption keys; without those keys, our data is ciphertext.
  • An attacker who compromises a single service (e.g. one AI worker pod) and tries to pivot to other tenants’ data. Multi-tenant isolation is enforced at multiple layers; a pod compromise should not yield cross-tenant access.
  • A malicious or buggy operator who tries to read or modify data outside their authorized scope. RBAC at API + row-level security at DB block them; audit log records the attempt.
  • An attacker who gains read access to our backups, GCS buckets, or BigQuery exports. CMEK + application-layer encryption render the data unreadable without the keys.
  • A regulator or external party demanding cross-border data transfer. All data is physically located in Dammam; no copy exists outside KSA at any time, including transient inference.
  • A network observer between any two components (edge ↔ cloud, service ↔ service, browser ↔ cloud). All traffic is TLS 1.3 or mTLS; nothing in cleartext over any link.
  • An insider on our own team acting maliciously. No one person can read production data without leaving an audit trail; the audit log is hash-chained and tamper-evident.

What we don’t design against (out of pilot scope, plan for after):

  • A nation-state-level adversary with persistent foothold and zero-day capabilities. We’d need a separate, dedicated security program.
  • A physical attack on the GCP Dammam data center. That’s Google’s problem.
  • Side-channel attacks on the GPU (e.g. reading another tenant’s tensor from VRAM). Mitigated by single-tenant inference pods per request; not bulletproof for state-sponsored actors.

2. Principles

Three things drive every detail below:

  1. Defense in depth. No single control is trusted alone. If TLS is misconfigured, audit log still catches it. If RBAC has a bug, RLS still blocks the query. If a tenant ID is forged, application-layer encryption keys differ per tenant.
  2. Encryption with our keys, not theirs. We accept that data sits in Google’s storage. We do not accept that Google can read it. Customer-managed keys at minimum (CMEK), application-layer encryption for the most sensitive data (we hold keys in our KMS, never share with Google’s data plane).
  3. Multi-tenant isolation enforced multiple ways. RBAC at the API layer, row-level security at the database layer, per-tenant encryption keys for sensitive blobs, audit logging on every cross-tenant scenario. The blast radius of any single bug or compromise stays bounded.

3. Encryption in transit

Every byte that crosses any network boundary is encrypted.

LinkProtocolHow it’s enforced
Browser ↔ API gatewayTLS 1.3, HSTS-preloaded, only modern ciphersCloud Load Balancer policy; rejected handshakes go to deny + log
Browser ↔ MediaMTX (WebRTC)DTLS-SRTP (WebRTC’s built-in)Built into the protocol; we don’t ship an unencrypted path
Service ↔ service (intra-cluster)mTLS via service mesh (Istio or Linkerd, M02 decision)Sidecars enforce; any pod without a valid cert is unreachable
Edge ↔ CloudmTLS over WebSocket or gRPC, outbound only from edgeEdge certificate provisioned during onboarding, rotated every 90 days; cloud refuses any other mode
Cloud SQL / Memorystore / Pub/SubTLS 1.3 enforced server-sideGoogle enforces; we verify with periodic external scans
Camera ↔ EdgeRTSP / RTSPS over the building’s LANCameras themselves rarely speak TLS; the edge keeps this segment off the public internet entirely

No exceptions for “internal” traffic. Inside the GKE cluster is not a trust boundary. Compromised pods are too easy a pivot.

4. Encryption at rest — CMEK on everything

Every storage surface uses customer-managed encryption keys (CMEK) rooted in Google KMS in the Dammam region. The key material is generated in our KMS, never exported, and revocable at any time.

StorageEncryptionKey location
Cloud SQL PostgresCMEKKMS Dammam, per-instance key
Memorystore RedisCMEKKMS Dammam
Cloud Storage (frames, clips, models, floor plans)CMEKKMS Dammam, per-bucket key (one key per tenant × data class)
BigQuery datasetsCMEKKMS Dammam, per-dataset key
Pub/Sub topic dataGoogle-default at rest (transient) plus our application-layer encryption on payloads above a sensitivity threshold
Cloud LoggingCMEK on the logs bucketKMS Dammam
Secret ManagerGoogle-default (HSM-backed)Dammam
Edge supervisor / app containers (local SQLite store-and-forward)LUKS full-disk encryption + per-container AES key at the app layerKeys provisioned to edge from cloud at registration

4.1 Key rotation

  • CMEK keys rotate every 90 days, automated, with the old key retained for decryption of older ciphertext.
  • Application-layer keys (see §5) rotate every 30 days, automated, with key history retained per ciphertext blob.
  • Edge mTLS certificates rotate every 90 days, automated, with at-least-one-week overlap.
  • Compromise: any key suspected of compromise is revoked immediately; affected data is re-encrypted in a background job; impacted services may be briefly unavailable during the window.

4.2 Key custody

  • KMS keys are tied to a specific service account; no human has direct read access to key material.
  • Key administration requires a manual two-person review (Melissa + one cloud engineer hire) recorded in the audit log.
  • A break-glass procedure exists for emergency revocation (single-person, but heavily logged, alerts everyone).

5. Application-layer encryption for the most sensitive data

CMEK protects against external attackers; it does not fully protect against a Google or CNTXT insider who has access to the unwrapped key in memory during normal operation. For the highest-sensitivity data, we add a second encryption layer using keys we hold outside GCP’s data plane.

5.1 What gets app-layer encryption

  • Raw video clips of incidents (frames showing pilgrims, Civil Defence personnel, building interiors).
  • 3D building models (geometry that could be used for adversarial reconnaissance).
  • Floor plan images.
  • Tenant-identifiable metadata in audit logs (operator name, building address details).

5.2 How

  • Per-tenant AES-256-GCM data keys (DEKs).
  • DEKs wrapped by a tenant key-encryption key (KEK).
  • KEKs stored in a separate KMS keyring with stricter access controls than the CMEK keyring.
  • Encryption / decryption happens in our application code, in our own pods, not in GCP-managed services. Plaintext never appears in any GCP-managed storage or in any GCP-side processing step.
  • The wrapped DEKs travel alongside the ciphertext (envelope encryption pattern).

5.3 Inference handling

The awkward case: AI inference must see plaintext frames to analyze them. We address this by:

  • Decrypting only in the AI worker pod (which is our GKE pod, not a managed Google inference service).
  • Holding the frame in memory only for the duration of inference.
  • Never writing the plaintext frame to disk during inference.
  • Logging that inference happened but never logging the frame contents.

This is the principal reason we don’t use Vertex AI managed inference (the inference happens outside our pod, with Google holding plaintext) — see 01-architecture-summary.md §2.1.

6. The 100 GB “cloud rights” problem

GCP / CNTXT terms include a clause granting them rights to data stored in our buckets. Interpretation varies, but the safe read is: anything we put in their object storage as plaintext is fair game for their use. Mitigation:

  • All sensitive data is app-layer-encrypted before it touches GCS (per §5).
  • Logs and metrics that could contain customer data are scrubbed before they hit Cloud Logging. PII goes through a redaction filter; raw video frames never appear in logs.
  • BigQuery exports are app-layer-encrypted in fields containing PII; analytical queries operate on hashed or tokenized values where possible.
  • We never store a customer’s plaintext business data in Google’s data plane. If we have to, we apply app-layer encryption first. No exceptions.

7. Multi-tenant data isolation

7.1 At the API layer

Every API endpoint resolves a tenant_id from the JWT, attaches it to the request context, and uses it in every downstream call. Endpoints that touch multi-tenant data accept no tenant ID from the client; the only valid tenant scope is what the JWT asserts.

  • Tenant resolution happens in NestJS middleware before any controller code runs.
  • A request with no tenant context cannot reach any tenant-scoped controller (returns 401 or 403 with audit log entry).
  • A request with a tenant context that doesn’t match a queried resource (e.g. trying to read another tenant’s camera) returns 404 (we don’t leak the existence of resources outside the user’s scope).

7.2 At the database layer — Postgres Row-Level Security

Multi-tenant isolation is also enforced in Postgres. An SQL bug or injection in the API layer cannot return another tenant’s rows because the database itself refuses.

  • Every multi-tenant table has a tenant_id (or building_id where building_id → tenant_id is canonical) column with a non-null constraint.
  • RLS policies on every such table: USING (tenant_id = current_setting('app.tenant_id')::uuid).
  • The application sets SET LOCAL app.tenant_id = '<uuid>' at the start of every transaction (via NestJS middleware on the database connection).
  • A connection without an app.tenant_id setting cannot read any multi-tenant row (RLS returns empty sets).
  • A privileged “service” role bypasses RLS for cross-tenant operations (analytics jobs, fleet management) — used sparingly and audit-logged.

7.3 In Pub/Sub

  • Each message includes a tenant_id field.
  • Subscribers must filter by tenant when processing; cross-tenant fan-out is impossible at the subscription level.
  • Sensitive payloads inside the message are app-layer encrypted; the tenant ID is needed to fetch the right key.

7.4 In storage buckets

  • One bucket per (tenant, data-class) combination — e.g. wiqaia-bld-{tenant_id}-frames, wiqaia-bld-{tenant_id}-clips.
  • IAM bindings on each bucket reference the tenant’s service account.
  • A misconfigured bucket policy can leak one tenant — never multiple — and only that tenant’s CMEK-protected data.

7.5 In the AI worker

  • Inference pods process one frame at a time, with the tenant ID attached.
  • No tensor state from frame N is reused in frame N+1 (no batching across tenants; per-camera inference is isolated by pod replica selection or per-request seed).
  • Per-camera anomaly baselines (stored in Redis) are namespaced by tenant.
  • Tier 2 LLM prompts include tenant-scoped context; the model itself has no cross-tenant state.

8. Tamper-evident audit logging

Every state-changing action — and every access to sensitive data — writes an audit log entry.

8.1 What’s logged

CategoryExamples
Authenticationlogin (success/failure), logout, MFA challenges, password resets, token refreshes
Authorizationevery denied request (401, 403), every privilege escalation attempt
Data accesscamera view (which camera, by whom, for how long), incident detail view, audit log access
Data mutationcreate / update / delete of any record, with the before/after diff for sensitive fields
Configurationevery infrastructure or RBAC change, with actor, timestamp, and reason
Alertscreation, acknowledgement, dismissal, escalation, resolution; each with actor and reason
Fleetedge-gateway connection / disconnection, software updates pushed, certificate rotations
Key custodykey creation, rotation, revocation, every emergency-access invocation
Cross-tenant or privileged operationsevery service-role bypass of RLS, with justification

8.2 How the log is tamper-evident

  • The audit log table is append-only. No UPDATE or DELETE is permitted by any role except a rotation job that archives old partitions to immutable cold storage.
  • Each row contains a hash of the previous row’s contents + its own contents (Merkle-chain pattern). Verifying the chain confirms no row was modified or removed retroactively.
  • A daily job verifies the chain end-to-end and alerts on any inconsistency.
  • The audit log is written to a Postgres partitioned table and tee’d to Cloud Logging with CMEK encryption (defense in depth: two independent stores, both must be tampered to hide an action).
  • Audit log access is itself logged (recursively, in a separate partition).

8.3 Retention

  • Active audit log: minimum 12 months in Postgres (regulatory baseline).
  • Archive: 7 years in Cloud Storage Coldline with object retention locks (cannot be deleted before expiry, even by an account compromise).
  • Cryptographic timestamping of monthly archive segments via a trusted timestamping authority (post-pilot).

9. NCA mapping — assumed Critical/Sensitive classification

Pending formal confirmation, the plan assumes WiqAIa+ will be classified by the National Cybersecurity Authority as a Critical/Sensitive System. Practical mapping to NCA control frameworks:

9.1 NCA ECC (Essential Cybersecurity Controls)

Control areaOur implementation
Asset managementBuilding, camera, edge gateway, user — all in Postgres with status and ownership; quarterly inventory review
Identity & accessKeycloak with MFA for admin roles; RBAC at API + RLS at DB; audit log on all access
Data protectionCMEK everywhere; app-layer encryption for sensitive blobs; key custody policy
CryptographyTLS 1.3 minimum; mTLS for service-to-service; AES-256-GCM for at-rest blobs; KMS-managed keys with 90-day rotation
Logging & monitoringAudit log (hash-chained), Cloud Logging (CMEK), Cloud Monitoring; daily review of anomaly alerts
Incident managementRunbook in docs/runbooks/incident-response.md (M10); on-call rotation; SLAs for containment & notification
Backup & recoveryDaily Cloud SQL automated backups; weekly DR drill
Network securityVPC isolation; zero-trust service mesh; egress restricted to known destinations
Vulnerability managementContainer image scanning in CI; dependency scanning weekly; OS patching automated on edge

9.2 NCA CCC (Cloud Cybersecurity Controls)

Control areaOur implementation
Data residencyAll data in Dammam; no Vertex AI; no cross-region replication outside KSA
Cloud provider assessmentGCP Dammam validated; CNTXT contract reviewed; documented
Cloud configurationTerraform-managed; baseline hardening per CIS GKE benchmark
Cloud access controlService accounts per workload; no human direct access to production data plane
Cloud monitoringAll audit logs from GCP (Cloud Audit Logs) routed to our SIEM (Cloud Logging)

9.3 NCA DCC (Data Cybersecurity Controls)

Control areaOur implementation
Data classificationThree classes: Public (none in this system), Internal (configs, KPIs aggregated), Confidential (everything customer-facing)
Data minimizationWe collect only what’s needed; cameras produce frames, not facial embeddings
Data retentionFrame snapshots ≤ 90 days; clips ≤ 1 year; audit logs ≥ 12 months active + 7 years archive
Data deletionBuilding-offboard procedure deletes all tenant data + verifies; tenant key revoked, ciphertext becomes unreadable even if a copy exists

10. PDPL (Personal Data Protection Law) alignment

PDPL requirementOur posture
Lawful basisCivil Defence safety mandate; documented in the building onboarding agreement
Data minimizationNo facial recognition. Person detection produces anonymous bounding boxes only. Crowd counting is aggregated.
Storage limitationRetention policies above; frames not associated with detections are not stored
Cross-border transferNone. All processing in Dammam.
Right to access / erasureImplemented at the data layer per tenant; supported procedurally for individuals on case-by-case basis (Civil Defence operations rarely admit individual-subject access for live surveillance, but the platform supports the request flow)
Breach notification72-hour reporting per PDPL; our incident response runbook (M10) enforces this with an escalation path
DPO contactTBD — designate before formal pilot launch

11. Incident response posture

A security incident is anything that may have caused unauthorized access to data, key material, or system control. The runbook (M10 deliverable) defines:

  • Detection. Cloud Logging anomaly alerts; audit log inconsistency alerts; failed-auth burst alerts; key access anomalies; user-reported.
  • Triage. Severity scale (S1 catastrophic → S4 informational); on-call decides within 15 min of detection.
  • Containment. Revoke affected keys; isolate compromised pods; revoke user sessions; block source IPs as appropriate.
  • Eradication. Identify root cause; patch; redeploy clean.
  • Recovery. Restore from backup if needed; rotate all potentially-exposed keys; observe for re-occurrence.
  • Notification. Customer notification within contractual SLA; regulator notification per PDPL (72 hr) and NCA timing; internal post-incident review.
  • Lessons learned. Written within one week; gap-closure tickets filed; the relevant plan doc updated.

The runbook lives in docs/runbooks/incident-response.md (M10). Until then, the placeholder rule is: any suspected incident pages Melissa + cloud engineer hire immediately, full stop.

12. Backup and disaster recovery

  • Postgres: Cloud SQL automated daily backups, 30-day retention; weekly point-in-time-recovery drill; HA standby in another zone within Dammam region.
  • GCS: Versioning enabled on every bucket; bucket-level lock on audit-log archive bucket.
  • Redis: Memorystore RDB snapshots daily; tolerable to lose for cache use cases, alarms-state replayed from Postgres on recovery.
  • Secrets: Replicated to two Secret Manager regions where available; otherwise documented manual recovery procedure.
  • Edge supervisor: Local state is self-recovering — re-registers with cloud and resyncs.
  • Recovery objectives: RTO 15 min for core API; RPO 1 min (no more than 60 seconds of data loss in worst case). DR drill quarterly (more frequently during pilot).

13. Penetration testing & vulnerability management

  • Continuous: Container image scanning in CI (Trivy or equivalent); fail PR on critical CVE.
  • Continuous: Dependency scanning (Dependabot, pnpm audit, pip-audit, govulncheck); weekly review of advisories; SLA for critical patches.
  • Quarterly: External pen test by an NCA-authorized provider, scoping the API, web app, and edge gateway. First pen test scheduled within 3 months of pilot go-live.
  • Annually: Red team exercise simulating advanced persistent threat scenarios.
  • Post-pilot: Bug bounty program for responsible disclosure.

These are not in the LOA scope (per the contract, cybersecurity certifications are excluded from the subcontract). They are nonetheless run because the broader business depends on them.

14. The non-compliance kill switch

Bad configurations are common. We catch them before they ship.

  • Terraform plan runs through a policy engine (e.g. OPA Gatekeeper or tfsec) that fails on:
    • Any storage bucket without CMEK.
    • Any Cloud SQL instance without CMEK and HA.
    • Any service that exposes a public IP not in an allow-list.
    • Any Pub/Sub topic without an explicit subscription IAM binding.
    • Any service account with broader-than-necessary permissions.
  • CI fails the PR if any check fails. No reviewer can override without a documented exception.

15. What’s deferred

Items we acknowledge are important but defer past the pilot, with explicit triggers:

  • Hardware-rooted device identity for edge boxes (TPM-backed). Trigger: third building onboarded.
  • Per-tenant KMS keyrings with cryptographic separation. Trigger: 10 tenants.
  • End-to-end encryption with customer-supplied keys (bring-your-own-key). Trigger: first enterprise customer who asks.
  • Formal SOC 2 / ISO 27001 audit. Trigger: any tender that requires it.
  • In-Kingdom git, in-Kingdom error tracking, in-Kingdom CI build runners. Trigger: NCA audit notice.

Document the trigger event in a ticket the day the trigger fires. Don’t let deferred work become forgotten work.

WiqAIa+ Docs · Rizoma