Skip to content
Updated Jun 9, 2026

PII-at-Rest Confidentiality

Status: draft · Owner: Chakshu · Companion to:2026-05-26-pii-chinese-walls-design.mdDecides: how PII is stored so that no single admin can extract it.

This is the confidentiality face of the pii-vault. The companion doc is the access face (tokenisation V, surrogates σ, blind index β, detok gated by OPA Φ) and defends T1/T2 + T3-reading-op-tables. This doc is what sits underneath: how the vault's plaintext system-of-record is stored against privileged insiders.


1. Property & adversaries

Target: no single party can (i) exfiltrate keys, (ii) bulk-extract plaintext, (iii) unilaterally change decryption policy.

A_db    DB admin              psql, reads all tables
A_app   service operator      deploys, reads service env + logs
A_infra host root             memory, disk, process introspection on dev-2
A_kms   one OpenBao custodian holds 1 of n Shamir shares

cls(f) sensitivity lattice is inherited from the companion doc; this doc protects cls(f) ≥ pii.


2. Crypto structure

per record r, sensitive field set:
  DEK_r           random 256-bit data key
  ct_r   = AES-256-GCM(DEK_r, plaintext, nonce_r)      nonce random per write
  wDEK_r = Transit.wrap(KEK, DEK_r)                     KEK never leaves OpenBao
stored:  { ct_r, wDEK_r, nonce_r, key_version }         DEK_r itself never persisted

decrypt:  app → Transit.unwrap(wDEK_r) → DEK_r → AES-GCM-open(ct_r)
          KEK stays in OpenBao; DEK_r + plaintext transit app memory (the accepted ceiling, §4)

OpenBao Transit (OSS - not the Enterprise Transform engine) provides wrap/unwrap with the KEK held inside Bao. OpenBao root/unseal is Shamir k-of-n.


3. Invariants

  • C1 (envelope). For cls(f) ≥ pii: plaintext persists only as AES-256-GCM(DEK_r); DEK_r persists only as Transit.wrap(KEK, ·); KEK never leaves OpenBao.
  • C2 (no single admin). unseal, KEK rotation, and Transit policy change each require Shamir k-of-n ⇒ no lone custodian/admin can bring up, rotate, or re-scope decryption.
  • C3 (path separation). Automated path = per-record unwrap, rate-limited (≤ R) + field-scoped
    • audited. Bulk / export / break-glass = quorum-gated + high-priority audit. No automated bulk-decrypt path exists.
  • C4 (separation of duties).{Shamir custodians} ∩ {app operators} = ∅; {audit readers (compliance)} ∩ {operators} = ∅. One role can neither decrypt-and-hide nor approve-its-own break-glass.
  • C5 (accepted ceiling). Live-node skimming at rate ≤ R is detected, not prevented (prevention needs a TEE - declined). Control = rate cap + audit-anomaly alert (§9b of companion doc).

4. Adversary → outcome

AdversaryBulk-extract?Why
A_db (psql)notables hold ct + wDEK; no KEK, no DEK
A_app (one operator)noautomated path rate-limited + audited; bulk path quorum-gated (C3)
A_kms (1 share)nok-of-n; one share insufficient (C2)
A_infra (root, now)residualunsealed KEK sits in OpenBao process memory ⇒ root could dump it. Shamir still blocks cold unseal. Closed in Phase B.
A_infra (root, Phase B)noKEK non-extractable in hardware HSM/KMS
any single adminno (skim only)can at most skim ≤ R/record on a live node (C5)

5. Phasing

PhaseScopeCloses
A (now)envelope via Transit; per-record DEK; verify Shamir k-of-n unseal; C3 path separation (rate-limit + audit automated, quorum break-glass); C4 custodyA_db, A_app, A_kms
Bmove KEK behind hardware HSM / KMS (Transit backing key in HSM, or migrate)A_infra residual
Cper-subject key hierarchy ⇒ crypto-shredding: erasure = destroy the subject's DEKs, not a row scrub (GDPR Art. 17 as a key-destruction op)erasure cost + assurance

6. Determinations (this doc)

  • K1 - Envelope encryption with per-record DEKs; KEK in OpenBao Transit (KEK never leaves Bao). Not the Enterprise Transform engine.
  • K2 - Shamir k-of-n on unseal + key-admin + policy change. Custodians diverse and disjoint from operators (C4).
  • K3 - Two decryption paths only: automated per-record (rate-limited, scoped, audited) and quorum-gated bulk/break-glass. No automated bulk path.
  • K4 - Accept C5 ceiling (live-node skim) until/unless a TEE is adopted; mitigate by rate cap + audit anomaly.
  • K5 - HSM-backing (Phase B) is the committed path to close the A_infra residual; not deferred indefinitely.

7. Open questions

  • DEK granularity: per-record vs per-subject vs per-(subject,field). Per-subject unlocks crypto-shredding (Phase C) but complicates shared-record cases (a claim references member + provider).
  • Shamir (n, k): how many custodians, threshold, and who holds shares - does separation of duties (C4) require ≥1 non-engineering custodian (DPO / counsel / board)? Governance, not code.
  • Automated-path rate cap R: legitimate ceiling per service, and the anomaly threshold that pages compliance.
  • Crypto-agility: key_version is stored, but is rewrap-on-rotate lazy (on next read) or a batch job?

8. Not decided here

  • TEE / confidential computing (would close C5) - explicitly out by choice.
  • The access face (tokenisation, OPA gating) - lives in the companion doc.
  • Snowflake/BI pseudonymisation - separate, deferred.

Olly Health Insurance Platform