Cryptography¶
Statement¶
ClaimGuard relies on modern, well-vetted cryptographic primitives for the three places it holds or transmits sensitive material: user passwords, authentication tokens, and the secrets and data the application processes. Specifically:
- Passwords are hashed with bcrypt at cost factor 12 before storage. Plaintext passwords are never logged. A separate an admin-recovery field on the user record column exists today as part of an admin password-recovery feature inherited from earlier prototyping; this is flagged as a known gap below and slated for removal — see Authentication for the implications and remediation plan.
- Authentication tokens are JWTs signed with HMAC-SHA256 using a
≥32-byte secret loaded from GCP Secret Manager at boot. The server
refuses to start without a valid
JWT_SECRET. - Secrets at rest are stored in GCP Secret Manager, encrypted with Google-managed keys and accessed via the VM's least-privilege service account.
- Data at rest on GCP persistent disks and Cloud Storage buckets is encrypted by default with AES-256 under Google-managed keys.
- Cloud-control-plane traffic (every
gcloudcall, every Secret Manager fetch, every GCS access) uses TLS with Google-issued certificates.
Public user-facing TLS is in place on both production HTTPS
ingresses: https://claim-guard.dtectvision.ai (nginx + Let's
Encrypt) is the canonical user URL today, and
https://app.dtectvision.ai (GCP HTTPS LB + Google-managed cert,
with Cloud Armor + a MODERN SSL policy enforcing a TLS 1.2 floor)
is the parallel API ingress. Both have HTTP→HTTPS redirects and
HSTS one-year. See
Encryption in transit.
Implementation¶
Password hashing¶
User passwords are hashed with bcrypt (cost factor 12) at registration
and password-change time, and verified with bcrypt.compare at login.
The cost factor is centralized as a single constant
(BCRYPT_ROUNDS = 12 in server/src/routes/users.js).
- The bcrypt digest in the
password_hashcolumn is the canonical authentication credential — login verifies against it viabcrypt.comparein constant time. - Password material is never logged.
- A separate an admin-recovery field on the user record column was introduced for an admin password-recovery feature in an earlier phase of the product. It stores the plaintext alongside the bcrypt hash and is exposed via the user-detail endpoint to admins within the same org and to super-admins for any org. This is a known gap — slated for removal so the bcrypt-only story is the actual story; see Authentication for the remediation plan and impact.
The 12-round cost factor is the OWASP-recommended default as of 2026 and gives roughly 250 ms of CPU time per hash on the production VM, which is the right trade-off between brute-force resistance and login latency for an interactive product.
JWT signing¶
User authentication tokens are JSON Web Tokens (RFC 7519) signed with
HMAC-SHA256 (the jsonwebtoken library default) using a key loaded
from JWT_SECRET.
JWT_SECRETis mandatory at boot: the server inserver/src/middleware/auth.jsexits non-zero if the variable is unset. There is no development fallback. Tests that need a value set one explicitly.JWT_SECRETis sourced from GCP Secret Manager (secret name the JWT secret) — see Secrets management.- Token lifetime defaults to 24 hours (
JWT_EXPIRY=24h), configurable per environment. - Tokens are stateless — there is no server-side session store. Logout is client-side token disposal.
Asymmetric signing (RS256 / EdDSA) is not currently used. Moving to asymmetric signing would let multiple verifiers (e.g., a future API gateway, a future tools-service) verify tokens without sharing the signing key; tracked as a roadmap item.
Secrets at rest¶
Runtime secrets — JWT_SECRET, DATABASE_URL, API_KEY — are
stored in GCP Secret Manager. Google encrypts secret payloads at rest
with AES-256 under Google-managed keys. The application reads them at
boot through the VM's attached service account
(the workload service account), which holds
roles/secretmanager.secretAccessor per secret. See
Secrets management for the
full bootstrap path and audit trail.
Data at rest¶
- VM persistent disk (the boot disk, the boot disk of the production VM): GCP encrypts every persistent disk by default with AES-256 under a Google-managed key. We do not currently use a customer-managed key (CMEK).
- Snapshots of the boot disk inherit the same encryption. See Backups.
- GCS buckets (the application's image-upload buckets used by the content-provenance service): encrypted at rest under Google-managed keys.
- Postgres on the VM: data files reside on the encrypted boot disk; the database itself does not perform per-row encryption (no TDE). Application-layer column encryption for sensitive PII is not currently in place; see Roadmap.
Data in transit¶
- Internal traffic on the VM (Node backend ↔ Python tools) goes
over
127.0.0.1only. It does not leave the VM. Documented as a deliberate design choice indocs/security/PROTOCOL.md §4. - GCP control-plane traffic (Secret Manager, Cloud Logging, GCS, Compute APIs) uses TLS with Google-issued certificates, validated against the system trust store. The application has no plaintext outbound paths to GCP.
- Outbound HTTP to operator-configured destinations (webhooks,
upstream APIs) is wrapped by the outbound-HTTP wrapper,
which enforces TLS for
https://URLs, blocks private/link-local/CGNAT ranges, and refuses redirects by default. See SAST for the full invariant set. - Public user-facing traffic today reaches the application
via two parallel HTTPS ingresses, both with HSTS and HTTP→HTTPS
redirect:
claim-guard.dtectvision.ai(nginx + Let's Encrypt, canonical user URL) andapp.dtectvision.ai(GCP HTTPS LB + Google-managed cert, API-only) — the LB's target HTTPS proxy enforces aMODERNSSL policy (TLS 1.2 floor) since 2026-05-06.
Algorithm choices summary¶
| Use | Algorithm | Notes |
|---|---|---|
| Password hashing | bcrypt cost-12 | OWASP-recommended baseline. |
| JWT signing | HMAC-SHA256 (HS256) |
jsonwebtoken library default; ≥32-byte secret. |
| Disk + GCS at rest | AES-256-GCM (Google-managed) | GCP default. |
| Secret Manager at rest | AES-256 (Google-managed) | GCP default. |
| TLS for outbound | TLS 1.2+ (system trust store) | Node.js / Python defaults. |
| Public user-facing TLS | Let's Encrypt via nginx on claim-guard.dtectvision.ai (canonical user URL) + Google-managed cert via HTTPS LB on app.dtectvision.ai (API ingress). HSTS + HTTP→HTTPS redirect on both. Cloud Armor on the LB. LB target HTTPS proxy enforces MODERN SSL policy (TLS 1.2 floor) since 2026-05-06. |
— |
Status¶
implemented — verified 2026-05-06.
What's in place:
- bcrypt-12 for passwords, end-to-end.
- JWT HS256 with mandatory secret bootstrap and Secret Manager backing.
- Secrets at rest in Secret Manager.
- GCP at-rest encryption for disks, snapshots, GCS, and Secret Manager.
- TLS for all GCP control-plane traffic.
- the outbound-HTTP wrapper enforces TLS for outbound application HTTP.
- Public user-facing TLS via two parallel HTTPS ingresses:
nginx + Let's Encrypt at
claim-guard.dtectvision.ai(canonical user URL today) and GCP HTTPS LB + Google-managed cert atapp.dtectvision.ai(API ingress, withMODERNSSL policy enforcing TLS 1.2 floor). HTTP→HTTPS 301 redirect on both. HSTS one-year + includeSubDomains on both. Cloud Armor (rate limit + Adaptive L7-DDoS protection) on the LB. - Plaintext password column (an admin-recovery field on the user record) — present in the the user account store to support admin password recovery. Documented in detail in Authentication. Tracked for removal; the doc names the migration steps. Until that ships, admins can read peers' plaintext passwords and a database compromise would expose them.
- CMEK — no customer-managed encryption keys for compute, GCS, or Secret Manager. Tracked as a later compliance cycle item.
- Application-layer encryption of sensitive PII columns in Postgres is not in place. Risk-rated low at current scale; revisit before the first regulated-customer onboarding.
Roadmap¶
- JWT to RS256 / EdDSA — asymmetric signing once we have more than one verifier.
- CMEK — for snapshots, GCS, and Secret Manager. Larger effort; revisit at SOC 2 Type II / ISO time.
- Column-level encryption for high-sensitivity fields, contingent on the first regulated-customer requirement.
- JWT_SECRET rotation cadence — scheduled in the JWT-rotation runbook. Rotation invalidates every issued token, so it requires comms. The operational playbook (when, who, how, blast radius, recovery) is written up in the JWT_SECRET rotation runbook; the first rotation under that runbook lands at the next 90-day boundary or sooner on suspected leak.