# A Stolen Token Is Useless. A Stale One Still Works.

**Published:** 2026-08-20  
**Author:** Kin Lane  
**Canonical:** https://apievangelist.com/2026/08/20/a-stolen-token-is-useless-a-stale-one-still-works/

I closed [the DPoP post in my Germany series](https://apievangelist.com/2026/07/28/never-trust-the-token-dpop-and-sender-constraining-for-government-apis/) with a line I still stand behind: Zero Trust is not a product you buy, it is a set of properties you can point at, and "a stolen token is useless" is one of the most important ones on the list. Michał Piszczek read that post and replied with an essay called [Token Revocation Is Not an Endpoint](https://piszczek.pl/blog/token-revocation-is-not-an-endpoint), and it is the best response I have gotten to anything in that nine-post series. It is not a correction of my post so much as the next room in the same house — the room I walked past. I want to give it a proper read here, because the argument deserves a wider audience than a comment thread, and because when I point it back at Germany's blueprint it exposes something I had not looked at closely enough.

Start with what he does in his own first paragraph, which set the tone for everything after it: he corrects a sentence *he* wrote. He had previously said "DPoP stops replay, the gap most teams miss is revocation lag once the token walked," and he opens by calling the second half right and the first half too loose. If only the token walked, a correct DPoP implementation should make it worthless to the thief. The hard problem starts when the attacker has the token *and* its proof key, or can execute inside the client process, or owns the device, or is riding the live session. RFC 9449 says this outright — if hostile code is running in the client execution context, DPoP's security guarantee is gone, because malicious code does not need to extract the key when it can simply ask the legitimate signing context to mint valid proofs for it. Sender-constraining limits a credential's *portability*. It does nothing about *stale authority*. Those are two separate axes, and his sentence for it is the one I have not been able to put down: **a token can be cryptographically valid and operationally unauthorized.**

That distinction is where self-contained JWTs get expensive. A signature proves that a trusted issuer made a statement and that nobody altered it. It does not prove the issuer still wants that statement honored right now. Validate only the signature and the embedded claims and the token stays good until `exp` even after the account is disabled, the role is stripped, or the device is declared compromised. So revocation is not a cryptographic operation at all — you cannot un-sign bytes that are already in the wild. Revocation means introducing fresher state at the enforcement point than the token carries. And RFC 7009, the token revocation spec itself, is honest about the gap: invalidation can have a propagation delay during which some servers know and others do not. That HTTP 200 you get back means the endpoint processed your request without erroring. The RFC deliberately returns the same response for a token that never existed. It is not proof that anything stopped.

His structural contribution is splitting "the token is revoked" into the four different systems that sentence quietly compresses:

- **Decision plane** — a person, policy engine, or identity provider records that a token, grant, session, device, workload, or key is no longer trusted. Reason, scope, effective time.
- **Distribution plane** — the changed decision travels via event streams, introspection responses, replicated deny state, config stores, cache invalidations. This is how fast every region can learn the truth.
- **Enforcement plane** — gateways, APIs, sidecars, workers, and connection handlers consult that truth and deny the next use. This is where fail-open behavior, stale caches, and mismatched policy versions become exploitable.
- **Effects plane** — work already admitted gets stopped or contained. Queued jobs cancelled, streams closed, delegated credentials invalidated, irreversible side effects prevented while there is still time.

You can succeed completely in the first plane and fail in the other three. The database row says `revoked_at=now()` while a second region serves a cached active response, an already-authorized WebSocket keeps carrying commands, and a background worker finishes a privileged export. The endpoint is healthy. The security outcome is not.

From that he gets a metric worth stealing outright. Not "the revoke endpoint returned in 80 milliseconds," but **Revocation Completion Time**: the interval from the central revocation decision to the moment the last execution path covered by policy is confirmed unable to exercise that authority. And RCT only means anything with a declared coverage set — "all enforcement points" is not an inventory, you have to name the gateways, services, regions, connection types, queues, and delegated systems that are inside the guarantee, and then partition completion by risk class, because 99% coverage is a failure if the missing 1% is the payments API. His example of a real service-level objective is the kind of sentence I wish more architecture documents contained: *p99 RCT under five seconds for privileged writes across every first-party region.* Compare that to "tokens revoke quickly," which he correctly calls marketing copy.

Then he pushes it into agents, and this is the part that made me sit up. For a human session, a thirty-second revocation delay allows two or three meaningful actions. An agent makes hundreds of tool calls in that same window, spawns subagents, and continues asynchronously. The identity mechanism did not get weaker — the execution rate multiplied the cost of its stale window. So he prices the delay: **Revocation Exposure = Revocation Completion Time × accepted action rate × average irreversible impact**, and if you cannot honestly normalize impact, drop the last term and report *actions at risk*, which still beats a naked TTL. At one hundred accepted actions per second, a fifteen-minute access token is not "short-lived" in any operational sense — it has room for ninety thousand actions inside it. That reframes an entire industry reflex. "Use short-lived credentials" only bounds exposure when the rate and impact of the actions inside that time are also bounded. I made [a version of this argument about broken object-level authorization](https://apievangelist.com/2026/08/12/agents-turn-broken-auth-into-a-breach-at-machine-speed/) — same flaw, radically different blast radius once the thing knocking on the door does not get tired. He is making it about the emergency brake, which is the harder and more useful place to make it.

The last piece is that revoking one string is usually the easy part, because of what that string already built. A compromised session may have minted refresh tokens, exchanged an access token for a delegated one, created an API key, opened a WebSocket, scheduled an export, started a workflow, added an administrator, or issued a presigned URL. Authority forms a graph — session to refresh-token family to access tokens to delegated credentials to jobs and side effects — and a real kill switch needs a defined closure over it. Does "revoke this agent" mean one token, one run, one device, the whole token family, every subagent, every delegated capability, every running job, or everything the run created? If that answer is not explicit, your endpoint works exactly as implemented while the incident continues down a child edge nobody wrote down. And he draws the honest line at the end of it: you can stop a token from authorizing the next request, but you cannot cryptographically un-send an email, un-disclose a secret, or un-transfer money. Revocation cannot reverse physics. Compensation, cancellation, and quarantine live in the effects plane, which is why irreversible work needs idempotency keys, delayed commit boundaries, or approval gates.

Now point all four planes back at Germany, because that is what I actually owe this series.

The blueprint holds up well on the first three, and better than most architectures I read. The decision plane is genuinely strong: a dedicated PAP as the only authorized write path to the policy store, a PDP that owns the decision and the audit log, and [Principle 17 written into their constitution](https://gitlab.opencode.de/sachsen-anhalt/mid/foederale-api-autorisierungsinfrastruktur) — dynamic permission control, rights can be changed or revoked instantly, never statically hard-wired. They wrote the requirement down. The distribution plane is where they are further ahead than nearly anyone: ADR-022 chose the OpenID Shared Signals Framework plus Security Event Tokens precisely because they standardize both the event format and the cross-organizational exchange mechanism, which is exactly the "push risk changes" layer Michał recommends building. The enforcement plane is real rather than assumed, because ADR-012 does not leave coarse-grained validity to local JWT inspection — it is a mix of the signed token identifier *and* a PDP call, which is the architecture that gets you fresh state at the door instead of a five-minute stale window.

The interesting gaps are the ones his vocabulary makes visible.

First, ADR-023 chose selective polling, including long-polling, OPA-bundle-style, for PDP synchronization. That is a completely reasonable engineering choice. It is also, in his framing, an authorization budget: the polling interval is the maximum time an old allow decision stays usable after policy changes, and that makes it a security parameter, not a performance setting. It deserves to be recorded as one. Second, both of the ADRs that carry the revocation story — 022 for SSF and SET, 021 for the Trillian/Tessera transparency log — carry explicit re-evaluation gates on implementation maturity. That is the intellectual honesty I praised in [the ADR post](https://apievangelist.com/2026/08/06/show-your-work-governing-an-api-standard-with-adrs-and-attacker-models/), and it means the propagation guarantee is real on paper and still unproven in the field, which is precisely where a measured RCT belongs. Third, and this is the one I would raise if I were in the room: the effects plane is quiet, in Germany's documents and in nearly every other architecture I read. Their stack includes RFC 8693 token exchange, which by definition manufactures delegated descendants. A federated infrastructure spanning sixteen states and independent operators has queues, long-lived connections, and cross-boundary delegation everywhere. What happens to work already in motion when a coarse-grained permission is withdrawn is a question the architecture has not answered in public yet. It should be an ADR.

I have to point the same argument at my own tooling, because it lands there too. The [API Authorization Profile](https://apicommons.org/api-authorization) I built out of this research has a rule, AZ-META-2, that checks whether an authorization server *advertises* revocation and introspection endpoints per RFC 7009 and RFC 7662. That is worth checking. It is also, in his terms, a decision-plane check with nothing at all to say about the other three planes — and the profile's own README says as much, that it validates the declared contract and not the running system. Michał's essay is the sharpest argument I have read for keeping that caveat loud rather than letting a green Spectral report imply more than it earned. A static ruleset can tell you the door has a lock advertised on the label. It cannot tell you how long the door stays open after you turn the key.

There is a lesson underneath all of this that I run into constantly at a completely different layer of the stack. I spend a lot of my time proving that an HTTP 200 is not evidence — that a documentation page returning 200 does not mean an OpenAPI exists, that a live developer portal does not mean the definitions are reachable, that you have to fetch the thing and diff it against a control before you can claim anything. He is making the identical argument one layer down: a 200 from `/revoke` is an intention, not proof. The only proof is measuring the last path that keeps working. His test design follows from that and is the right one — issue a credential, exercise it through every region and enforcement path, trigger revocation, and keep sending requests until each path rejects, then check refresh reuse, delegated tokens, open connections, queued jobs, and credentials the original session created. Then run the whole thing again while delaying the event bus, dropping a notification, restarting a service, skewing a clock, taking the deny store offline. Report p50, p95, p99, and maximum, because the mean is decorative when an attacker only needs the slowest usable path. He calls it chaos engineering for authorization, which is exactly what it is, and it converts "we support revocation" from a checkbox into something falsifiable.

Go read [the whole essay](https://piszczek.pl/blog/token-revocation-is-not-an-endpoint) — it is far more detailed than my summary, it is careful about separating finished standards from drafts and research, and he built [a Revocation Exposure calculator](https://piszczek.pl/tools/revocation-exposure) that turns the argument into a number for your own stack. This is what I want out of publishing in public: someone reads the thing you wrote, agrees with the part that was right, names the part that was incomplete, corrects their own earlier sentence along the way, and hands the whole conversation a better vocabulary than it had before. Sender-constraining was the right answer to the question I asked. Revocation is the question I should have asked next, and now the German blueprint has a four-plane checklist to be measured against rather than admired.
