The trust boundaries in Transit, how secrets are kept out of source, and how database access is granted and proven.

Reference Value
Subject and boundary Public versus private surfaces, secret handling, network exposure, SQL reader roles and vulnerability reporting. Header policies are on ‣; variable names on ‣.
Verified source infra/postgres-serving-access, docker-compose.yml, .gitleaks.toml, SECURITY.md at main 9733eb26d92533f56cdc8bc46b81b93fbb5987c8, checked November 10, 2026.
Support state Implemented controls. Applying roles or HBA to a real database is an owner-approved operator action.

Trust boundaries

Surface Exposure Holds secrets
transit-web, transit-data-proxy Public Internet No. An R2 binding that their code only reads, a service binding and public variables
data.yesid.dev (R2) Public read No. CORS allows GET and HEAD only
VM services (postgres, caddy) Bound to 127.0.0.1 by default Database role passwords, feed keys and storage keys in the runtime environment
GitHub Actions Private Database URL, storage keys and Cloudflare credentials as Actions secrets and the production environment

Public transit data comes from published snapshots; neither public Worker connects to PostgreSQL. Address search has a separate external geocoding path described below.

Address-search boundary

GET /api/geocode checks same-origin request provenance before parsing input. It accepts exactly one nonempty q, limits the original input to 160 Unicode code points, rejects control characters and unsupported modes, and requires exactly one provider present in the public catalog with a readable manifest. Origin/Referer and Fetch Metadata checks are request-provenance guards, not user authentication.

geoCa.ts sends address text, provider-local context and language through the Transit server to the fixed https://geolocator.api.geo.ca/ endpoint, then filters returned coordinates to the provider bounds. Successful and not-found geocoding responses use Cache-Control: private, no-store; rejected requests also use no-store. This path reads no operational database.

When available, Cloudflare rate-limit bindings handle trusted-IP or shared missing-IP requests. If a binding is absent, fails or returns an unusable result, bounded in-memory token buckets apply separately per Worker isolate. Missing trusted IPs share one local bucket. The fallback is not a durable or globally coordinated limit; rejection returns 429 with Retry-After.

Device-storage disclosure

The EN/FR privacy copy identifies theme, motion, map-panel state/size and the bulk section preference as localStorage data. sections-choice stores the visitor's Expand all / Collapse all choice across pages and visits. Individual toggles are view-local; the TOC and independent tools use their declared state. Navigation scroll positions use sessionStorage for the browser session. The preference is a local UI setting, not a transport-data or address-search history. The released section preference tolerates storage access failures.

Secrets in source

Network exposure

PostgreSQL and the Caddy health proxy bind to loopback unless POSTGRES_BIND_ADDRESS or CADDY_BIND_ADDRESS say otherwise; changing either is a reviewed exposure change. Caddy serves plain HTTP on :80 inside the container unless CADDY_SITE_ADDRESS enables TLS. For remote health checks keep the loopback bind and use SSH: HEALTH_BASE_URL=http://127.0.0.1:8080 HEALTH_SSH_TARGET=<target> bash scripts/validate-oracle-cutover.sh. scripts/verify-runtime-tls.py checks runtime trust and loopback TLS offline without changing host trust.

Operator access to the VM