Trust & Security

Cohiva Credentials security controls, subprocessors, and service commitments

Uptime & Data Residency

Availability target
99.5% monthly uptime
Recovery time objective
4 hours (recovery time objective)
Recovery point objective
24 hours (recovery point objective, Cloud SQL automated backups)
Data residency
australia-southeast1 (Sydney, Australia) — all tenant data at rest

Security Controls

15 controls across 7 categories. Updated 2026-06.

Encryption Data Isolation Access Control Audit Data Minimisation Secrets Management Observability
Category Control Evidence
Encryption Encryption in transit All API and web traffic is served over HTTPS/TLS 1.2+. HTTP is not supported in production. GCP Cloud Run managed TLS; trust page served on HTTPS origin
Encryption Encryption at rest All tenant databases are encrypted at rest using AES-256 at the storage layer. Cloud SQL AES-256 encryption at rest (platform-managed)
Data Isolation Database per tenant Every customer tenant has its own dedicated Postgres database. No shared connection pool or shared schema. A bug in one tenant's DB query cannot touch another tenant's data. db-per-tenant architecture; createDb() bound to TENANT_DATABASE_URL per deployment
Data Isolation Row-level security on every table Every issuer-scoped table has FORCE RLS enabled. An unauthenticated or cross-issuer query returns zero rows — no data is accidentally exposed. Migration-first FORCE RLS on every table; db.test.ts verifies cross-issuer isolation
Access Control API key authentication All issuer API access requires a SHA-256-hashed API key. Keys are never stored in plain text and are only shown once at creation. api_keys table; SHA-256 hashed on creation; issueApiKey() domain function
Access Control Rate limiting with 429 + Retry-After All public endpoints are rate-limited per API key (or IP) with a standard 429 response that includes a Retry-After header. Prevents abuse and protects tenants from noisy-neighbour effects. @fastify/rate-limit; S-PUBSEC-01 standard; api.test.ts asserts 429 + Retry-After
Audit Append-only audit log All credential lifecycle events (issue / revoke / reissue / GDPR erase) are written to an append-only audit_log table. UPDATE, DELETE, and TRUNCATE are blocked at the database level by a trigger — the log cannot be silently altered. audit_log, verification_events, engagement_events — S-TRUNCATE-01; db.test.ts asserts TRUNCATE rejection
Audit Credential lifecycle immutability Revocation and expiry are reflected in real time without erasing the credential record. The effective status is computed at serve time from the stored row, so the history is always intact. effectiveStatus() in domain/status.ts; revokedAt / expiresOn columns; verify page reflects status instantly
Data Minimisation No raw recipient email storage Recipient email is NEVER stored or logged raw. Only a per-issuer salted SHA-256 hash is stored. A GDPR erase nulls the display name and revokes credentials; the salted hash is a retained pseudonymous tombstone. recipientHash in credentials table; domain/ingest.ts; domain/gdpr.ts; hardening.test.ts asserts raw email never returned
Data Minimisation GDPR subject access + erasure POST /api/v1/gdpr/export returns a recipient's pseudonymous data (hash + label + credentials, never the raw email). POST /api/v1/gdpr/erase nulls the personal label, revokes all their credentials, and logs the event. domain/gdpr.ts; hardening.test.ts
Secrets Management No secrets in source control Signing keys, API tokens, and database connection strings are never committed. A CI gate (npm run check:secrets) scans every commit and blocks merges if any secret pattern is detected. scripts/check-secrets.sh; CI step check:secrets; STUBS.md lists all operator-gated secrets
Secrets Management GCP Secret Manager for production secrets All production secrets (TENANT_DATABASE_URL, SendGrid key, signing keys) are stored in GCP Secret Manager and injected at runtime. None appear in environment variables, config files, or container images. cloudbuild.yaml --update-secrets; infra/terraform/secrets.tf secret shells; docs/DEPLOYMENT.md env/secret matrix
Observability Structured request logging Production API logs are structured pino/JSON with a per-request correlation ID (x-request-id). Raw email addresses and secrets never appear in logs. server.ts logger config; domain/notify.ts log scrub
Observability Prometheus metrics + OpenTelemetry bootstrap GET /metrics serves Prometheus-format metrics (issued total, verified total, HTTP request count + latency histogram). OpenTelemetry traces are exported when OTEL_EXPORTER_OTLP_ENDPOINT is configured. domain/metrics.ts; GET /metrics; CRD-12c
Observability Health + readiness probes GET /health confirms liveness. GET /health/ready confirms the DB is reachable before accepting traffic. Used by Cloud Run startup/liveness probes. server.ts; /health and /health/ready endpoints; cloud-run.service.yaml probe config

Subprocessors

Third-party services that process customer data on our behalf.

Processor Purpose Location Data types
Google Cloud Platform Application hosting (Cloud Run), tenant database hosting (Cloud SQL), secrets storage (Secret Manager), container registry (Artifact Registry) australia-southeast1 (Sydney, Australia)
• Tenant databases
• Application secrets
• Container images
• Application logs
SendGrid (Twilio) Transactional email delivery — credential issuance notifications, earner portal login codes, renewal alerts United States
• Issuer email address (from-address config)
• Recipient email address (transient in memory — never stored; used only to address the outbound email)
GitHub / GitHub Actions Source code hosting, CI/CD pipeline (build, test, deploy) United States
• Application source code
• Build artefacts
• Deployment configuration (no secrets — these are in Secret Manager)

Contact

Security questions: security@cohiva.com
Trust documentation last reviewed: 2026-06
Machine-readable controls: GET /api/v1/trust/controls