Immutability, WORM and what an attestation means
Two words get conflated whenever backups and immutability are discussed, and conflating them is how an auditor ends up over-credited. This page keeps them apart. One guarantee is always in force and is a property of the platform. The other is stronger, lives in the object store, and the engine will only credit it after it has actually checked.
The first guarantee is tamper-evidence. Every archive is sealed with post-quantum hybrid encryption, signed with a post-quantum hybrid signature, and chained in a signed run log so that rolling an archive back to an older state is detectable. This holds for every destination, regardless of how the bucket was created. The second guarantee is store-enforced S3 Object-Lock, which makes an object physically undeletable within its retention window even by a compromised delete-credential. That one is not assumed. The engine runs a live capability probe against the bucket, and it asserts Object-Lock only when the probe confirms the bucket enforces it.
Everything below is taken from the report generator, the posture check, the destination probe and the keyless attestation in the engine. Where the code is careful, this page is careful in the same place.
Two guarantees, kept distinct
The immutability report (buildImmutabilityReport in reports.ts) is built around exactly this split, and it never collapses the two.
The platform baseline is unconditional. The report states it for every configured destination as tamper-evidence: each archive is signed with a post-quantum hybrid signature and chained in a signed run log with anti-rollback. That sentence is true whether or not the bucket has Object-Lock, because it is a property of how the archive was written, not of how the bucket was provisioned.
Store-enforced Object-Lock is the separate, stronger property, and it is conditional. The report asserts it only when the live probe has returned a confirmed-enforced verdict for the bucket. When the probe confirms enforcement, the per-destination line reads as store-enforced WORM with the lock mode and retention window, and adds that a compromised delete-credential cannot hard-delete an archive inside that window. When the probe has not confirmed enforcement, the report falls back to the tamper-evidence baseline and, where a policy was configured, says plainly that store-enforced Object-Lock is not in force.
An attestation of properties in force, never a blanket WORM claim
The immutability report is an attestation of the recoverability properties actually in force for your account at the moment it is generated. It always states the tamper-evidence baseline, and it asserts store-enforced Object-Lock only when the probe confirms enforcement. It is never written as an unqualified WORM or immutable claim, because the platform cannot honestly make one for a bucket it has not been able to verify.
The three honest WORM states
WORM status is fed by a live capability probe, not inferred from a refused delete. The probe is the S3 GetObjectLockConfiguration path (objectLockStatus in s3-read-ops.ts), a bucket-root read that asks the store what the bucket itself enforces. A refused delete proves only that one credential lacked permission this once; the probe reads the actual configuration, which is why the engine relies on it instead.
The probe feeds the immutability posture check (buildImmutability in posture-checks.ts, with posture.ts fixing its severity at medium), which resolves to one of three states. Only one of them is a failure, and that failure is the dangerous case.
| State | What the probe found | Verdict |
|---|---|---|
| Enforced | The bucket enforces Object-Lock; the probe returned the lock mode and, where set, the bucket’s default retention window | Pass: store-enforced WORM is real |
| Configured but not enforced, or unconfirmed | A WORM policy is configured, but the bucket does not enforce Object-Lock (it was not created with it), or the probe could not confirm enforcement, or the configured policy is invalid | Fail (the dangerous gap) |
| Not configured | No WORM policy is configured and the bucket enforces nothing | Pass, informational: tamper-evidence still holds, Object-Lock is not claimed |
The middle row is the one that matters. A WORM policy can be configured while the bucket silently ignores it, because S3 Object-Lock can only be enabled when a bucket is created and cannot be turned on afterwards. If the policy is set but the bucket was not created with Object-Lock, the retention headers are accepted and discarded by the store, and nothing is protected even though an operator may believe it is. The check fails here on purpose, as a medium-severity warning, rather than passing on the strength of a policy that is doing nothing.
The “unconfirmed” reading folds into the same failing state for the same reason. If the probe could not read the configuration (the store did not answer, returned something unparseable, or the destination cannot answer the probe at all), the engine treats it as cannot-confirm, never as enforcing. A native R2 binding destination is one such case: the binding cannot read Object-Lock, so its probe returns unknown, and a console-set R2 destination is reached over the S3-compatible endpoint instead so it can be both set and probed as S3.
A configured WORM policy is not the same as archives being write-once-locked
If you have set a WORM policy but the immutability check is failing, your archives are tamper-evident but they are not write-once-locked. The store is ignoring the retention headers because the bucket does not enforce Object-Lock, or the engine could not confirm that it does. Do not read a configured policy as protection. Object-Lock cannot be enabled on an existing bucket, so the fix is to re-create the destination bucket with Object-Lock enabled and set a valid policy (a mode plus a positive retention window), then confirm the check passes. An invalid policy, where the mode and a positive retention window are not both set, also fails here: no lock metadata is written at all.
The two lock modes differ in who can shorten retention. Governance mode lets a privileged, auditable override bypass retention; compliance mode prevents deletion for the window for everyone, including the root account. Compliance is the strong ransomware-resilient setting, and a wrong window under it cannot be undone, so it is chosen deliberately.
What a keyless attestation is
A keyless attestation is the cheapest recoverability signal the engine offers, and the most broadly available one. It is attestKeyless in keyless.ts, re-exported from reader.ts for backward compatibility, reached through POST /admin/restore/attest. It needs no decryption key and reads no record’s plaintext, so it runs even in the break-glass-only posture where the engine holds no in-account read-back key. It establishes three facts about a sealed run, and each is computed without any decryption key.
Signature valid
The stored root manifest verifies under the operator-pinned public verifier, a verification key and never a decryption key. Because that one signature covers the Merkle root, every per-record record hash, the declared counts and the shard digests, a valid signature means the run’s committed structure is authentic.
Complete
Every shard the signed root lists is present, and its bytes hash with SHA-384 to the value the signed root pins, and the declared shard and record counts are well-formed. So no shard has been dropped, truncated or swapped relative to the signed run.
Not rolled back
The signed, append-only run log verifies and places this run in the freshness chain with no index gap or fork. Unless staleness was explicitly acknowledged, the run is also the latest for its downpipe, so the archive has not been rolled back to an older state.
Every field is honest about what it could reach. A check that could not be evaluated, for instance because the signature failed and so the body must not be trusted as authoritative, reports false rather than a fabricated pass. The reason returned with a failure is a short, coarse, secret-free note on the first failing check, drawn from a fixed enumeration so that no shard id or object key ever leaks into the response or the log.
What a keyless attestation is not
This is where the careful reading earns its place, because the natural misreading is the one to head off.
A keyless attestation is not a Cloudflare-side or store-side verification of your recoverability. The object store is not asked to vouch for anything. The check is performed by the in-account engine, and it amounts to engine-side completeness plus signature-validity plus anti-rollback over the operator-pinned public verifier. Nothing in the store’s API is treated as proof of recoverability, and no decryption key is used at any point.
A keyless attestation is also not a full per-record recompute. It attests the per-record fields through the single signature that covers them, together with the shard-presence completeness check, but it does not open each record and recompute its hash and the Merkle tree from the plaintext. That stronger recomputation needs the manifest key and so belongs to the keyed tiers, the blind restore test that decrypts and verifies every in-scope record. A keyless attestation is the broadly available floor, not the keyed ceiling.
Engine-side, not a store-side check
Read a keyless attestation as the engine, inside your own account, confirming three things about a run’s signed structure with no decryption key in hand. It is not the object store confirming your data is recoverable, and it is not a full record-by-record decrypt-and-verify. Both of those are different, stronger statements, and only one of them, the keyed blind restore test, opens your records at all.
How this connects to coverage
A passed keyless attestation is enough to mark a resource protected. When POST /admin/restore/attest returns a clean three-flag result, the engine stamps a restore-proven record for the covering downpipe with the method recorded as a keyless attestation (stampRestoreProven in router-audit.ts, called from router-restore.ts). On the coverage ladder, a downpipe with at least one successful run and a restore-proven record reads as protected.
This is deliberate, and it is also a precise claim rather than an absolute one. Because a keyless attestation alone can stamp the record, a resource can be marked protected without a full keyed blind restore having run against it. Proven recoverability here means a restore-proven record exists, set by either a passed blind restore test or a passed keyless attestation; it does not always mean every record was decrypted and verified. The coverage view never reads protected as an unqualified guarantee, for exactly this reason.
Where each statement comes from in the engine
The report split lives in buildImmutabilityReport and its wormProperty helper in reports.ts: the tamper-evidence baseline is stated for every configured destination, and the store-enforced Object-Lock line is added only when the probe’s bucketEnforces verdict is true. The three-state posture check is buildImmutability in posture-checks.ts, with posture.ts fixing its severity at medium, where an enforced bucket passes, a not-configured bucket passes informationally, and a configured-but-unenforced, unconfirmed or invalid policy fails. The live probe is objectLockStatus, the S3 GetObjectLockConfiguration read in s3-read-ops.ts (called from s3.ts), with the native R2 binding returning unknown by design in r2.ts. The router gathers the WORM signal best-effort in gatherWormSlice, so a probe fault degrades to unknown and never fails a report or posture read. The keyless attestation is attestKeyless and the KeylessAttestation type in keyless.ts, re-exported from reader.ts for backward compatibility, and the coverage stamp on a clean result is stampRestoreProven in router-audit.ts, called from router-restore.ts. Retention pruning is deferred under the break-glass-only posture (no in-account read-back key to compute the orphan set safely). Where a bucket enforces compliance-mode Object-Lock, the store itself refuses a delete inside the retention window, so write-once retention and pruning never fight at the destination.
Related reading
For the integrity chain the engine builds when it finalises a run, the chain a keyless attestation later verifies, read verify at seal.
For how a passed attestation moves a resource to protected, and why protected is a precise claim, read coverage and gaps. For the keyed counterpart that decrypts and verifies every record, read prove recoverability.
For why the audit log is tamper-evident in the same detection-not-prevention sense, and why it pairs with bucket-level immutability, read the audit log. For the broader rule that the engine never claims an assurance it cannot evidence, read precise claims and honesty.
Last updated .