Offboard with crypto-shred
Erase an org's data irreversibly over the operator API: open a grace window, then destroy the org's encryption key so every value encrypted under it becomes permanently unreadable, and sweep the dead rows.
This guide covers org offboarding: the two-step erasure lifecycle, what each step destroys, and what it leaves alone. Read Data residency first for why the keys are per-org in the first place.
What crypto-shredding destroys
Every controlplane value that an org owns is encrypted at rest under that
org’s own
key, named org-<id>-secrets in the operator KMS
(internal/kms/crypter.go, OrgKeyName). The key encrypts only key-wrapping
material: each stored value carries its own data encryption key, and the org
key wraps that. Destroy the org key and the wrapped keys can no longer be
unwrapped. Every value under the key becomes permanently unreadable, and database
access cannot recover it.
CompleteErasure destroys the key first and sweeps the dead rows second
(internal/controlplane/offboarding/offboarding.go). If the process crashes in
between, the leftover rows are unreadable ciphertext. They are not readable
orphans.
The sweep deletes four row families from the controlplane database:
- policy settings for the org
- policy agent overrides for the org
- config values for the org
- config agent overrides for the org
After the sweep the org’s status becomes erased. The shred is complete the
instant the key is gone; the rows are only tombstones that the sweep drops.
The two steps
Offboarding is a two-step lifecycle, so an accidental trigger can be caught before anything is destroyed:
active --begin--> destroying --complete--> erased
The first step opens the grace window. It marks the org destroying and
destroys nothing:
curl -X POST -H "Authorization: Bearer $TOKEN" \ https://<controlplane>/api/v1/orgs/<org>/erasure{"org_id":"<org>","status":"destroying"}A 202 response confirms the request. The status code is not 201 or 200
because the destructive half has not run. There is no timer on the grace
window; it stays open until you complete it. There is also no documented cancel
route that closes the window and returns the org to active: no erasure
endpoint undoes the begin step. The only path that sets an org back to
active is a side effect of the org upsert: POST /api/v1/orgs with the
same id and an empty status writes the org row with status active
(internal/controlplane/iam/api.go, createOrg; internal/controlplane/iam/store.go,
UpsertOrg). That route exists for creating orgs, not for canceling erasure,
so treat it as an undocumented recovery path and not a supported cancel.
The second step is the irreversible one. It destroys the org key, sweeps the
rows, and marks the org erased:
curl -X POST -H "Authorization: Bearer $TOKEN" \ https://<controlplane>/api/v1/orgs/<org>/erasure/complete{ "org_id": "<org>", "status": "erased", "key_shredded": true, "rows_swept": 12}Complete the erasure only from the grace window. Calling it on an org in any
other state is a 409 conflict, so a single call can never destroy an org.
The key_shredded value reports the outcome accurately. It is false when no
operator KMS is configured, or
when the KMS provider cannot delete keys (the provider reports
ErrUnsupportedOperation). In those cases the rows are still swept, but the
ciphertext left behind was not key-shredded, and the response reports that
with key_shredded: false. A KMS
error during the destroy aborts the erasure before any row is swept.
Who can run it, and what is refused
Both routes require the platform:orgs capability at platform scope over the
bearer-authenticated operator API. See
Operator API for the authentication model. An
agent-operator caller is refused at the actor-kind check before grants are even
loaded. The platform org itself cannot be erased: the shred would destroy the
operator’s own key material, so the route returns 403 before touching
anything. An unknown org is 404.
Every step is audited at authority tier. The controlplane.org.erasure.begin
event is durably captured before the status commit, so a destruction is never
unrecorded. controlplane.org.erasure.complete reports key_shredded,
rows_swept, and the final status. See
Audit events for the field lists.
What the erasure does not destroy
Erasure is narrower than it looks. Know these four boundaries before you run it.
The org row survives. The status becomes erased; the org still exists.
Deleting the org row is a separate, idempotent step:
curl -X DELETE -H "Authorization: Bearer $TOKEN" \ https://<controlplane>/api/v1/orgs/<org>The platform org is undeletable as well as unerasable. A DELETE on an org
that does not exist is a 204 either way.
Users, groups, grants, and service accounts are not swept. The erasure sweep covers policy and config rows only. Those identity rows keep existing until you delete the org or remove them by their own routes.
The enclave is never dialed. The controlplane holds no credential for the
enclave’s KMS and performs no operation against it. The org’s sovereign
secrets and sealed inference bodies live in the enclave’s own database under
the enclave KMS’s per-org key. The enclave-side store implements the same
crypto-shred (internal/enclave/sovereign/store.go, CryptoShredOrg), but no
API exposes it yet. Today, destroying an enclave’s recoverable data means
tearing down the enclave and destroying its KMS keys, as described in
Data residency.
A deployment with no operator KMS has no shred to report. Policy and
config values are then stored in plaintext. Erasure still sweeps the rows, and
it still destroys nothing else, but key_shredded reports false because no
key existed to destroy.