Skip to content

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 gcloud call, 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_hash column is the canonical authentication credential — login verifies against it via bcrypt.compare in 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_SECRET is mandatory at boot: the server in server/src/middleware/auth.js exits non-zero if the variable is unset. There is no development fallback. Tests that need a value set one explicitly.
  • JWT_SECRET is 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.1 only. It does not leave the VM. Documented as a deliberate design choice in docs/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) and app.dtectvision.ai (GCP HTTPS LB + Google-managed cert, API-only) — the LB's target HTTPS proxy enforces a MODERN SSL 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 at app.dtectvision.ai (API ingress, with MODERN SSL 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.