How it works: privacy by design
A status-list service sits between a credential's issuer and its verifiers, and sees things neither side normally sees about the other. Most of this service's design decisions exist to stop that vantage point from becoming a surveillance opportunity, for an outside observer and for the operator of this service itself.
The leak a shared status list can't fully avoid
Some things aren't avoidable: this service learns which issuer allocated which index in which list the moment it happens, the same way a certificate authority knows who it issued a certificate to. That mapping has to exist somewhere for revocation to work at all.
What is avoidable is that mapping becoming exploitable by anyone watching traffic instead of holding the database, including the operator watching which URLs get fetched. draft-ietf-oauth-status-list-21 publishes a status list as one raw bit-packed array, not a per-index lookup API. Every design decision below addresses what that format choice exposes and how to close it.
Randomized index allocation
A credential's index within its list is derived with a keyed format-preserving permutation (a small Feistel network over the list's bit-length, with cycle-walking to land back in range), not handed out sequentially. A sequential counter would leak issuance order and, over time, issuance volume: anyone watching a list fill up linearly could infer roughly how many credentials a given issuer population was producing and when. The permutation is bijective and keyed per list, so index order is indistinguishable from random to anyone without that list's key. It runs in O(1) time and space, with no stored shuffle array needed.
Issuer-blind list placement
The privacy goal covers more than the index inside a list; it also covers what the choice of list reveals. If a list were effectively dedicated to one issuer, or a handful of low-volume ones, an operator watching verifier requests would learn "this verifier just checked something from issuer X" without ever decoding the list's contents. Fetching that URL alone would be the leak.
Allocation instead maintains a pool of several concurrently-active lists and assigns each new credential using power-of-two-choices: pick two lists at random from the active pool, allocate into whichever has more remaining capacity. The selection function never sees issuer identity, only the current fill level of two randomly-sampled lists, which keeps the mixing unbiased. Over time each list's membership reflects overall issuance traffic rather than any single issuer's.
Anonymity-set-aware rotation
A list rotates out of active use once it fills up or hits a configured age limit — but a list that rotates out while still nearly empty is itself a small-anonymity- set problem: a handful of entries in a list is barely better than no mixing at all. Rotation is guarded against ending a list's active life while its membership is still too small, extending the window (up to an administrative ceiling) rather than rotating a near-empty list out on a timer alone.
Decoy noise: herd immunity for real revocations
Because allocation itself never writes anything (a fresh index simply reads as
VALID until something changes it), every non-VALID byte
in a published list used to be unambiguous: it could only mean one thing, a
credential its issuer had revoked or suspended. Downloading a list and diffing it
over time would let an observer count, and potentially correlate, real revocation
events.
The service can inject camouflage noise into never-yet-allocated capacity:
periodically flipping a random sample of unused indices to a real
INVALID or SUSPENDED state too, much as chaff traffic
obscures real traffic in an anonymity network. Decoy transitions deliberately
mirror real ones: a suspended decoy can revert to valid, matching a real lifted
suspension, and an invalid decoy never changes again, matching a real revocation's
permanence. That makes a decoy statistically indistinguishable from a real entry.
This is opt-in and off by default in every deployment today; see the limitations
below.
What this doesn't solve
A few limits worth stating directly:
- The issuer → list → index mapping this service records at allocation time is real and, by necessity, known to the service's own operator. Nothing above removes that. It only stops that knowledge from being exploitable by someone watching traffic instead of holding the database.
- Decoy noise is opt-in, off by default, and has not yet run against real production traffic. It is a real, tested mechanism, not a placeholder, but its effectiveness at real-world scale is still an open question.
- Network-level metadata (source IP, request timing, TLS fingerprinting) is outside this service's control. The mitigations here address what the application-layer data itself reveals, not network-layer traffic analysis; a CDN in front of the read path is meant to help with that separately.
This page summarizes the reasoning. The full design document, including the trade-offs considered and rejected along the way, is public at docs/design.md in the service's own repository.