Threat model

Assets

  • Secret plaintext values (highest value).
  • The KMS master key and derived DEKs.
  • Admin tokens.
  • The audit trail (integrity).

Trust boundaries

  • Network client ↔ API (HTTP).
  • API ↔ PostgreSQL (libpq).
  • API ↔ AWS KMS.
  • Primary ↔ standbys (WAL stream, managed by pg_replica).

Actors

  • Anonymous network client — may reach the API port.
  • Read client — an allowlisted service that fetches/verifies secrets.
  • Admin — holds an admin token; manages secrets.
  • DB-only adversary — can read Postgres files / WAL / a standby, but not the KMS key or the API process memory.
  • Insider with table write — can modify rows in vault.*.

Threats and mitigations (STRIDE-style)

Threat Mitigation
Spoofing a read client by IP IPv4/CIDR allowlist, fail-closed. Proxy IP trusted only when VAULT_TRUST_PROXY is set behind a trusted proxy.
Spoofing an admin High-entropy bearer tokens; only SHA-256 fingerprints stored; constant-time check; revocation.
Tampering with stored ciphertext AEAD tag fails open; AAD binds ciphertext to name:version so rows can’t be swapped between secrets/versions.
Tampering to escalate read access RLS FORCEd + REVOKE FROM PUBLIC; only the scoped service role can touch rows.
Repudiation Append-only vault.audit_log with actor/IP/action/outcome.
Information disclosure from DB/WAL/standby theft Only ciphertext + wrapped DEK at rest; decryption requires the KMS key (separate trust domain).
Information disclosure via error messages Internal errors return a generic 500; crypto/DB detail is logged server-side only.
Information disclosure via timing on /verify Constant-time comparison of presented value vs decrypted versions.
DoS via large bodies 1 MiB request body limit; secret value size cap.
Elevation via rotation worker races rotation_jobs claimed with FOR UPDATE SKIP LOCKED; stale claims reclaimed; rotation is a primary-only write.
Nonce reuse weakening AEAD 192-bit random XChaCha20 nonces, fresh per version; uniqueness covered by tests.
Key material in memory after use Zeroizing DEKs/master keys. The DEK cache holds unwrapped keys for up to VAULT_DEK_CACHE_TTL_SECS (never plaintext); lower or disable the TTL to shrink the window.

Residual risks / open items

  • TLS to Postgres is not enabled in the scaffold (NoTls).
  • API TLS termination must be added (proxy or in-process).
  • Admin-token bootstrap flow (initial token provisioning) is operator-defined.
  • A KMS outage makes reads/writes fail closed (availability vs confidentiality trade-off) — adjust caching to avoid rate limits
  • The IP allowlist is IPv4-only by spec; IPv6 clients are denied.