Description
On a consent withdrawal, ec_finalize_response writes a tombstone row for whatever identifier the request's ts-ec cookie names. write_withdrawal_tombstone performs an unconditional write_entry(..., Overwrite) in crates/trusted-server-core/src/ec/kv.rs, and the cookie is only shape-checked — is_valid_ec_id verifies 64 hex, a dot, and 6 alphanumerics, with no signature recomputation.
The tombstone exists to stop later reads of a real row by batch-sync clients during the 24-hour revocation window. Writing one for an identifier the graph never held enforces nothing, while still consuming a write and occupying a keyspace entry with a TTL. Since the identifier arrives in a client-supplied cookie, that write is the client's to trigger, and the random 6-character suffix is not covered by the HMAC, so each variation is a distinct row.
Recomputing the HMAC does not address it: the suffix is unsigned, so it can be varied freely once a valid prefix is known, and a user whose IP changed between issuance and withdrawal would fail the check and have a genuine opt-out discarded.
Determining existence needs care in both directions:
- A Fastly lookup is eventually consistent, so a stale "missing" would discard a withdrawal for an identity issued moments earlier.
- Counting keys by prefix is not an existence check — any longer key starting with the identifier answers for it.
- If existence cannot be determined at all, writing regardless restores the unconditional write whenever the store can be made to fail.
Separately, log_id truncates by byte with get(..8).unwrap_or(ec_id). When byte 8 splits a character the fallback emits the whole identifier, and the Fastly lookup error embedded the full key in its message.
Expected behavior
- Only an identity the graph holds is tombstoned.
- Existence is determined exactly and with a strongly consistent read.
- A store that cannot answer never causes a write for an identity that was never issued, and never silently discards a genuine withdrawal.
- The browser cookie is expired in every case — it is the primary enforcement.
- A redacted identifier is never emitted in full.
Affected area
Core (Edge Cookies, GDPR)
Done when
Description
On a consent withdrawal,
ec_finalize_responsewrites a tombstone row for whatever identifier the request'sts-eccookie names.write_withdrawal_tombstoneperforms an unconditionalwrite_entry(..., Overwrite)incrates/trusted-server-core/src/ec/kv.rs, and the cookie is only shape-checked —is_valid_ec_idverifies 64 hex, a dot, and 6 alphanumerics, with no signature recomputation.The tombstone exists to stop later reads of a real row by batch-sync clients during the 24-hour revocation window. Writing one for an identifier the graph never held enforces nothing, while still consuming a write and occupying a keyspace entry with a TTL. Since the identifier arrives in a client-supplied cookie, that write is the client's to trigger, and the random 6-character suffix is not covered by the HMAC, so each variation is a distinct row.
Recomputing the HMAC does not address it: the suffix is unsigned, so it can be varied freely once a valid prefix is known, and a user whose IP changed between issuance and withdrawal would fail the check and have a genuine opt-out discarded.
Determining existence needs care in both directions:
Separately,
log_idtruncates by byte withget(..8).unwrap_or(ec_id). When byte 8 splits a character the fallback emits the whole identifier, and the Fastly lookup error embedded the full key in its message.Expected behavior
Affected area
Core (Edge Cookies, GDPR)
Done when