CC6.6 — Logical access via boundary protection (firewalls + reverse proxy)
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 implements logical access security measures to protect against threats from sources outside its system boundaries. The control covers the network-perimeter posture (firewall rules, ingress allow-listing), the reverse-proxy / TLS-termination layer, the publicly exposed surfaces, and the auditing of ingress traffic.
How ZeroAuth meets this control
The production network boundary is documented in docs/threat_model.md "Threat surface inventory". The VPS at 104.207.143.14 exposes:
- Port 22 (SSH) — key-only,
root(laptop key) +zeroauth-deploy(CI key) authorized. UFW open to internet. - Port 80 (HTTP) — Caddy listening; redirects to HTTPS.
- Port 443 (HTTPS) — Caddy listening; reverse-proxies to the app docker container on internal port 3000.
All other ports are UFW-blocked. Postgres (5432) and Redis (6379) bind to the docker network only — no external exposure.
The TLS-termination + reverse-proxy layer is defined in Caddyfile at the repo root. Caddy serves api.zeroauth.dev (the central HTTP API, including /v1/*, /api/console/*, /api/admin/*, /api/health, /api/leads/*, the demo SAML/OIDC routes when enabled, and /dashboard static bundle), docs.zeroauth.dev (Docusaurus), and zeroauth.dev (marketing landing). TLS certificates are automatic via Let's Encrypt / Caddy's ACME integration.
Public surfaces, by audience:
/v1/*— tenant API key authenticated; scope-checked per endpoint per CC6.1 + CC6.3./api/console/*— public forsignup+login; JWT-cookie authenticated for the rest. Rate-limited per IP viaauthLimiter./api/admin/*— single sharedx-api-key; read-only./api/health— unauthenticated; subsystem-status only./api/leads/*— unauthenticated; marketing forms; writes only to theleadstable./api/auth/saml/*+/api/auth/oidc/*— demo stubs gated byENABLE_DEMO_AUTH=true; off in production;src/middleware/demo-auth-gate.tsreturns 503 when disabled. Threat-model rows A-03 + A-04.
CORS today is wildcard-allowed (Access-Control-Allow-Origin: * for unauthenticated routes, tenant-bounded for authenticated routes). Audit finding C-13 ("CORS is wildcard-allowed") is open-sprint-2; the closure replaces wildcard with a per-tenant allowed_origins allow-list.
The demo-bypass closure (commit 02e1734) is the most-recent boundary-tightening: the bypass that accepted did:zeroauth:demo:* DIDs without crypto verification has been removed, eliminating an "auth-grade ingress that didn't actually check anything" surface. The access-token-query-fallback closure (commit ee6aad4) eliminates JWTs leaking into Caddy access logs.
Caddy access logs are emitted in structured form and rotated by the host syslog daemon. Compliance-relevant access patterns (admin actions, console-JWT issuance, key issuance / revocation, proof-pairing submissions) write to audit_events through appendAuditEvent (commit a475ed8) — the hash chain + on-chain anchor (ADR 0013 + 0014, commit 27ed93c) makes the access trail tamper-evident.
Smart-contract perimeter: DIDRegistry, Groth16Verifier, and AuditAnchor are deployed on Base Sepolia (chain ID 84532). The contracts use OpenZeppelin's access-control patterns for onlyOwner writes. The deployer wallet is the single owner; npm run wallet:rotate is the rotation mechanism. HSM-backed signer migration is week 48 per the compliance roadmap.
Mobile-prover perimeter: the Android prover app communicates with the API over HTTPS only. Certificate pinning is in place at the prover (relevant to RBI DPS §7 mobile-app-security). WebView process isolation (commit e2579df, android:process=":prover") bounds the in-app perimeter as well.
Evidence references
docs/threat_model.md"Threat surface inventory" — full ingress inventory + UFW posture + public surfaces.Caddyfile(repository root) — TLS termination + reverse-proxy ruleset.docker-compose.yml(repository root) — service-binding posture (Postgres, Redis on docker network only).src/middleware/demo-auth-gate.ts— demo-auth gating off in production.docs/security/audit-findings.mdC-13 — open CORS-allowlist finding.- Commit
02e1734— demo-bypass closure (boundary tightening). - Commit
ee6aad4— access-token-query-fallback closure (logs leakage closure). - Commit
a475ed8—appendAuditEvent(audit trail for boundary events). - Commit
e2579df— WebView process isolation (mobile-app perimeter). - Commit
0224be4— server-side Play Integrity enforcement (mobile-app ingress check). docs/threat_model.mdrows A-03, A-04 — demo-auth gating threat surfaces.
Open gaps + remediation roadmap
- Per-tenant CORS allowlist — audit finding C-13 open-sprint-2; replaces wildcard.
- WAF in front of Caddy — under evaluation; cloudflare-vs-self-hosted decision target week 14 (2026-08-24).
- IPv6 posture — VPS currently IPv4-only; IPv6 enablement deferred to Phase 4.
- DDoS mitigation contract — under evaluation alongside WAF decision; potential Cloudflare engagement.
Test or audit query
From an external host: nmap -p 1-65535 104.207.143.14 | grep open should return exactly 22/tcp open, 80/tcp open, 443/tcp open. curl -sI https://api.zeroauth.dev/api/health returns 200. curl -sI https://api.zeroauth.dev/api/auth/saml/callback in production returns 503 (demo-auth gated off).