The CDE runs entirely on Cloudflare with envelope key custody, no HSM
Context
Having decided cardholder data lives only in an edge CDE (ADR 0006), two questions follow: on what platform does the CDE run, and how are its keys custodied to satisfy PCI DSS v4 for the data that protects account data (req 3.6/3.7). The keys in play are the RSA/ECDH card-decryption key at the edge and the KEK that seals PanTokens at rest in the control plane.
Two temptations had to be resisted: spreading the CDE across clouds (card page on one, decrypt on another), and assuming a hardware HSM is mandatory for card-key custody.
Decision
Run the whole CDE on one assessed platform, Cloudflare, and custody keys with envelope encryption under a separately-stored KEK — the third PCI-compliant method (req 3.6), no HSM — with an env switch to AWS KMS kept ready but off by default.
- All-Cloudflare CDE. The card page (Workers Static Assets), the decrypt/tokenize Worker, key custody (Cloudflare Secrets Store) and e-skimming monitoring (Page Shield) all sit on Cloudflare, whose PCI DSS v4 AoC names Workers and Secrets Store in scope. One platform → one AoC to inherit and one deploy model.
- Envelope encryption, KEK in a software vault local to each service. Each sealed blob uses a per-record random data key (AES-256-GCM); only the data key is wrapped by the KEK. The two sensitive keys live where their code runs and neither store reaches across: the card-decryption key in Cloudflare Secrets Store (with the Worker), the PanToken KEK in the control plane's own secrets (with
commerce-payments). Every blob carries akek_version(and the fingerprint carries its key version) so keys rotate without re-encrypting or migrating data. - KMS is a config switch, not a rewrite.
KEY_CUSTODY=secrets-store|kms(defaultsecrets-store) routes the data-key wrap/unwrap to AWS KMS when set; the envelope format is unchanged, so a hardware-HSM mandate is a deployment change, not a code change. Switching custody is a key-rotation event (a blob wrapped under one mode is not readable by the other). - Tamper-evident audit ledger. The control-plane audit log is append-only in practice: a Postgres trigger raises on any
UPDATE/DELETE/TRUNCATEof historical rows, and each row is hash-chained to its predecessor (rowHash = sha256(prevHash ‖ canonical(row))), so a tampering attempt is blocked outright, and any out-of-band edit remains detectable by a verifier walking the chain. (RevokingUPDATE/DELETEon the app role is an additional operator step, not the enforced mechanism.)
Consequences
- Positive: one PCI AoC and one deploy model for the whole CDE; no cross-cloud secret movement; key rotation is a version bump, not a data migration.
- Positive: the HSM decision is deferred cheaply — if an acquirer ever mandates one, flip
KEY_CUSTODY=kms(a Worker calls KMS directly over its signed HTTPS API) and keep compute, the card page and Page Shield on Cloudflare, rather than moving clouds. - Neutral / to revisit: Cloudflare attests it does not store/process/transmit cardholder data — the moment our Worker decrypts a card, that Worker is our CDE and our scope; the AoC covers the infrastructure beneath it, not our PAN handling. Cloudflare is leaned on for compute and e-skimming, not as a payment gateway or 3-D Secure provider.
- Neutral / to revisit: the KMS hybrid adds a cross-cloud call on the key path (amortized by caching decrypted data keys per rotation, not per charge) and an availability coupling to AWS; worth it only under a hardware-key mandate.
Alternatives considered
- Split platform (card page on Cloudflare, decrypt on AWS Lambda): rejected — two vendors, two AoCs, two deploy models and cross-cloud secret movement. Strictly more scope.
- All-AWS (CloudFront/S3 page, Lambda decrypt, KMS/CloudHSM key): the only all-in-one path to hardware-HSM custody, but AWS has no managed e-skimming product, so we would hand-build what Page Shield gives us on the most audit-sensitive surface. Kept as a documented fallback for a hardware-HSM mandate or portability — but the KMS-from-Cloudflare hybrid usually beats a full move.
- Hardware HSM for the card key: rejected as a default — PCI req 3.6 explicitly accepts a separately-stored KEK, and the acquirer agreement carries no HSM clause, so an HSM is an option, not a mandate. The
KEY_CUSTODY=kmsswitch preserves the option.