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—AuditAnchorper-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(commit27ed93c) — per-tenant chain. - ADR
0014-on-chain-anchor-cadence.md(commit27ed93c). docs/compliance/compliance-roadmap-v1.md§1.3 — cross-border DPA list.docs/threat_model.mdrows A-01, A-15, A-27 — cross-boundary threat surfaces.
Open gaps + remediation roadmap
- Direct-SQL-path cross-tenant test —
docs/threat_model.mdrow A-01 named gap. Target Phase 1 sprint 2 alongsideplatform.tstest 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.