Skip to main content

CC6.7 — Information movement across logical boundaries (cross-tenant + cross-jurisdiction)

Status: Implemented Owner: Agent #38 (Senior Compliance Lead, SOC 2 + ISO 27001) Last reviewed: 2026-05-28 Next review: 2026-08-28

Trust Services Criteria reference​

The entity restricts the transmission, movement, and removal of information to authorised internal and external users and processes, and protects it during transmission, movement, or removal. The control covers cross-tenant isolation, cross-jurisdictional transfer, transmission encryption, the audit trail of data movement, and the prevention of unauthorised data exfiltration.

How ZeroAuth meets this control​

The most consequential logical boundary at ZeroAuth is the per-tenant boundary. ZeroAuth is multi-tenant; each customer is isolated by (tenant_id, environment) per CLAUDE.md "Load-bearing capabilities" §1. Information must not move across this boundary.

The cross-tenant guard is layered:

Source-level guard. tests/tenant-isolation.test.ts (commit a1bbc47) walks every /v1/* route file and asserts every router.<verb> declaration carries the authenticateTenantApiKey middleware. 14 intentionally-public exceptions live in PUBLIC_ROUTE_EXCEPTIONS with ≥ 20-character justifications. A developer who tries to land an unauthenticated /v1/* endpoint trips the test before merge. Threat-model row A-01.

Schema-level guard. tests/schema-purity.test.ts (commit 5425032) locks down the tenant-scoped table columns. A developer cannot add a column that holds cross-tenant data without explicit revision of the lock-list. Closes the channel where a column-add slips past code review.

Query-level discipline. Every SQL query in src/services/platform.ts (and similar) takes (tenant_id, environment) as parameters and embeds them in the WHERE clause. Threat-model row A-01 mitigation field captures this; the named gap is the per-SQL-path test (the route-layer test exists; the direct SQL-path test is the open item).

Audit-chain partition. The audit hash chain (ADR 0013, commit 27ed93c + commit a475ed8) is per-tenant — there is one chain per tenant_id, not one global chain. A noisy tenant cannot delay another tenant's chain head; an attacker compromising one tenant's chain does not corrupt another.

On-chain anchor partition. Per-tenant per-day on-chain anchors via AuditAnchor (ADR 0014, commit d6c6a4e). The (tenantIdHash, dayUtc) is a unique key — write-once enforced by the contract — so anchors from one tenant cannot overwrite or interfere with another.

Cross-jurisdictional movement is governed by DPDP §13. The compliance roadmap §1.3 lists the three cross-border processors with a Data Processing Agreement (DPA) on file: GitHub (build/CI), Sentry (error reporting, scrubbed), Cloudflare (TLS termination on the marketing site). Each has a DPA referenced in the vendor-management runbook. R-COMP-08 (cross-border-transfer rule tightened) is the named risk; mitigation is the in-country-alternative pre-evaluation at docs/compliance/dpdp/cross-border-fallbacks.md (target week 8).

Transmission encryption: TLS terminates at Caddy for every public surface (api.zeroauth.dev, docs.zeroauth.dev, zeroauth.dev). Internal docker-network traffic between app + Postgres + Redis is not TLS-encrypted but is bound to the docker network (no external surface). The on-chain anchor traffic uses Base L2 RPC over HTTPS; 3 redundant RPC providers are used per ADR 0014.

The forbidden-biometric-payload guard (commit c09c081, tests/biometric-rejection.test.ts) prevents raw biometric data from crossing the ingress boundary. ADR 0016 (commit 76f8d4e) layers a runtime defence — the zod schema's .refine() rejects 9 forbidden payload keys (image, template, pixel, depth, frame, raw_face, raw_finger, biometric_data, photo) at parse time, so a generic JSON proxy cannot smuggle raw biometric past the named-field-read guard.

Demo-bypass closure (commit 02e1734) prevents an "any DID starting with did:zeroauth:demo:" from receiving the data-movement rights of a real tenant. Threat-model row A-27.

The audit trail of information movement is the audit_events table. Every endpoint that returns data writes a row through appendAuditEvent capturing tenant_id, actor, action, resource, timestamp. The hash chain (ADR 0013) makes the trail tamper-evident; the on-chain anchor (ADR 0014) makes it externally verifiable.

Evidence references​

  • CLAUDE.md "Load-bearing capabilities" §1 — multi-tenant isolation contract.
  • Commit a1bbc47 — tenant-isolation source-level guard (tests/tenant-isolation.test.ts).
  • Commit 5425032 — schema-purity test (column-add lockdown).
  • Commit a475ed8 — per-tenant audit chain implementation.
  • Commit d6c6a4e — AuditAnchor per-tenant per-day write-once anchors.
  • Commit c09c081 — biometric-payload source-grep guard.
  • Commit 76f8d4e — ADR 0016 (runtime forbidden-key defence).
  • Commit 02e1734 — demo-bypass closure.
  • ADR 0013-audit-log-hash-chain.md (commit 27ed93c) — per-tenant chain.
  • ADR 0014-on-chain-anchor-cadence.md (commit 27ed93c).
  • docs/compliance/compliance-roadmap-v1.md §1.3 — cross-border DPA list.
  • docs/threat_model.md rows A-01, A-15, A-27 — cross-boundary threat surfaces.

Open gaps + remediation roadmap​

  • Direct-SQL-path cross-tenant test — docs/threat_model.md row A-01 named gap. Target Phase 1 sprint 2 alongside platform.ts test file.
  • docs/compliance/dpdp/cross-border-fallbacks.md — R-COMP-08 mitigation; target week 8 (2026-07-20).
  • audit_events.action = 'cross_tenant_query_blocked' — defensive audit signal when the WHERE-clause guard fires. Row A-01 audit-signal gap.
  • Hyderabad DR replica — D-Q4-04, week 46; closes the data-residency-via-replication gap.

Test or audit query​

grep -E "WHERE.*tenant_id" src/services/platform.ts | wc -l returns the count of tenant-scoped queries (should be > 10 for the production service). cat tests/tenant-isolation.test.ts | head -30 shows the source-level guard and the public-route-exception list with ≥ 20-char justifications.