The key that refuses to talk to us
1 August 2026 · 8 min read · TruMint
Part 1 ended on a promise: your data is decryptable only inside a running production service. That is easy to say and hard to mean. Most systems that claim it are one IAM binding away from being false, because the key is granted to a service account — and a service account is just a name that an administrator can attach to anything, including a shell.
This post is about replacing that name with a measurement, and then being precise about the gap that remains.
The problem with granting a key to an identity
The usual design gives the production service account permission to use the key. It is a real improvement over a key file on disk, and it fails in a specific way: anyone who can create a VM with that service account attached, or mint a token for it, inherits the key. The permission describes who is asking, and “who” is administratively assignable.
What we want is a permission that describes what code is asking — one that a person cannot satisfy by pointing a credential at a different program.
Attestation: proving what is running
Production runs as a single container image inside a Confidential VM using AMD SEV, on Google Confidential Space. Memory is encrypted by the CPU; the host cannot read it. At boot the platform measures what it launched and issues a signed attestation token containing, among other claims, the SHA-256 digest of the container image.
That token is exchanged for cloud credentials through a workload identity pool whose condition we pin: the assertion must come from Confidential Space and carry the hardened support attributes, which excludes debug images where an operator could attach and inspect memory. The resulting principal is not a service account. It is the image digest itself.
┌──────────────────────────────────────────────────────────┐
│ AMD SEV Confidential VM · memory encrypted by the CPU │
│ │
│ container image sha256:65862c0b… │
│ │ │
│ ▼ │
│ platform measures the launch │
└────────┬─────────────────────────────────────────────────┘
│ signed attestation token
│ swname = CONFIDENTIAL_SPACE
│ support_attributes ∋ STABLE (no debug boot)
│ image_digest = sha256:65862c0b…
▼
┌──────────────────────────────────────────────────────────┐
│ Workload Identity Pool · attributeCondition enforced │
└────────┬─────────────────────────────────────────────────┘
│ principal = …/attribute.image_digest/sha256:65862c0b…
▼
┌──────────────────────────────────────────────────────────┐
│ Cloud KMS — key "field-kek", europe-west2 │
│ IAM allow list has exactly ONE member: that digest │
└──────────────────────────────────────────────────────────┘Locking ourselves out
Binding the key to a digest is only half the job. On a normal cloud project the owner role implicitly grants cryptographic operations — so the founder account could have used the key regardless of who else was on the allow list. Basic roles cannot be trimmed.
The fix is an organisation-level deny policy: deny the KMS use permissions to everyone, with a single exception for the attested workload pool. Deny beats allow, and the policy sits above the project, so a project administrator cannot remove it.
PERMISSION_DENIED: cloudkms.cryptoKeyVersions.useToEncrypt. The same account cannot even list the deny policy that stops it. If either of those ever starts working, something has changed that should not have.What a deploy has to do, and why it is awkward
If the key is bound to a digest, then shipping any code change — a copy tweak, a bug fix — produces a new digest that the key has never heard of. Deployment therefore includes a key re-grant, and that is deliberately the most sensitive step in the pipeline.
1 build image → sha256:NEW
2 grant NEW digest on field-kek ← alerts fire on this IAM change
3 boot new enclave beside the old (both serve traffic — no downtime)
4 probe NEW enclave directly: GET /api/health/crypto
└─ performs a real KMS unwrap + cipher round-trip
└─ not healthy ⇒ roll back the grant, keep the old enclave, stop
5 retire the old enclave
6 revoke every digest except NEW ← key answers exactly one image againStep 4 is the one that earns its keep. A root-level health check returns 200 from a service whose decryption is completely broken; only a probe that actually unwraps a key tells you the new image can do the job. Step 6 matters because a key that accumulates old digests is a key that answers several images, and the guarantee decays quietly.
Alerting on the sensitive step
Any IAM change on the key ring, and any mutation of the attestation pool, raises an alert. That is what makes the arrangement checkable rather than merely configured: the moment someone grants the key to a new digest, the fact is recorded and pushed.
What this does not prove
Here is the part that vendor architecture posts tend to leave out, and it is the reason we split this series into three.
1. We choose which digest gets the key
The deny policy stops us using the key. It does not stop us granting it. A determined operator could build an image that copies plaintext somewhere and grant that image the key — the attestation would be perfectly valid, because attestation proves which bytes are running, not that those bytes are trustworthy.
What stands in the way today is that the grant is a logged, alerted IAM change. That is a real control and an incomplete one: the alert currently goes to the operator, which is the person it is meant to constrain.
2. You cannot yet check that the digest matches our published source
A digest is only as meaningful as your ability to reproduce it. Our builds are not reproducible: the image records no source commit, and building the same code twice does not reliably produce identical bytes. So while we can tell you the running digest, you cannot independently derive it from source and compare.
This is the honest limit of the current design, and it is on the roadmap rather than done. Until it lands, the correct reading of the attestation chain is: only one image can decrypt, and we can prove which image that is — not that image is the code we published.
| Property | Status |
|---|---|
| Key unusable by any human, including the owner | Enforced and verifiable today |
| Key bound to a measured image, not an identity | Enforced today |
| Debug/inspectable boots excluded | Enforced by the attestation condition |
| Every key grant logged and alerted | Enforced today; alert routing is operator-only |
| Running image reproducible from published source | Not yet — roadmap |
| Alerts visible to users, not just to us | Not yet — roadmap |
Part 3 leaves the key custody question entirely and deals with the paths that go around it: the server at runtime, the AI features, and the administrator who can still take over an account.
- Part 1What we can and cannot read
- Part 2The key that refuses to talk to us (you are here)
- Part 3Where it stops
See it for yourself: explore a sample portfolio. No signup, no bank link.
Try the live demo