docs(spec): validate every claim on the token /revoke acts on - #19
Draft
antstanley wants to merge 1 commit into
Draft
docs(spec): validate every claim on the token /revoke acts on#19antstanley wants to merge 1 commit into
antstanley wants to merge 1 commit into
Conversation
antstanley
force-pushed
the
spec/validate-revoke-token-claims
branch
2 times, most recently
from
August 5, 2026 08:22
66bbd28 to
b096ad4
Compare
antstanley
changed the base branch from
main
to
spec/audit-and-throttle-auth-failures
August 5, 2026 08:22
antstanley
marked this pull request as draft
August 5, 2026 08:25
This was referenced Aug 5, 2026
antstanley
force-pushed
the
spec/validate-revoke-token-claims
branch
from
August 5, 2026 09:00
b096ad4 to
cfd6244
Compare
antstanley
force-pushed
the
spec/validate-revoke-token-claims
branch
from
August 5, 2026 11:29
cfd6244 to
9000429
Compare
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
antstanley
force-pushed
the
spec/validate-revoke-token-claims
branch
from
August 5, 2026 12:01
9000429 to
5d139d7
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
Stacked PR 5 of 16 — part of stack #35, which targets
main.Base: #24 (
spec/audit-and-throttle-auth-failures) · Followed by: #21This PR's diff shows only its own change. Merge the stack bottom-up; do not merge this before its base.
Why it sits at position 5: Supersedes #24's revoke-path audit variant (
SessionsRevoked→TokenRevocation) and inherits its mandatory channel. Establishessidas the session identifier, which #22 later re-points tofamily_id.Scope of this PR
This PR currently carries the change spec. It will also carry the implementation plan and the build work for that spec — plan and code land as later commits on this same branch, so the spec, its plan, and its implementation review and merge as one unit.
Review order within the PR: spec first, then plan, then implementation.
Change spec proposing full claim validation on
POST /revoke, and a bounded revocation authority.Problem.
crates/core/src/service/revoke.rsverifies the JWS signature and then acts on the token'ssub, without checkingexp,iss,aud, or token type. Any access token the service ever issued — including one expired months ago — is a repeatable, unauthenticated primitive that destroys every session belonging to that subject.Proposed delta. Replace the signature-only
verify_and_extract_subwith a single first-party validatorAppService::validate_access_token, sited next tobuild_access_token, which pins the header (alg/kid/typ: at+jwt), verifies the signature before reading any claim, and enforcesexp/iat/iss/aud(plusnbfwhen present, 60s skew) by deserializing into a typedAccessTokenClaimsso required-claim presence is a parse result rather than an omittable check.It also fixes the authority model rather than only the checks: a new required
sidclaim (the session's existingrefresh_token_hash, so no store migration and no new port method) binds each access token to the one session it was minted for. The access-token branch revokes that session;revoke_all_user_sessionsbecomes unreachable from the public endpoint and stays on the authenticated admin path.Failure is fail-closed: nothing is revoked,
ValidationFailedis emitted atNotice— above theDebugthat the defaultemit_threshold = "info"would drop, below the default blocking threshold so an audit outage cannot turn a 200 into a 503 — and the client still sees RFC 7009's indistinguishable 200.Notes.
RESERVED_CLAIMSmust gainsidandnbf, or a per-user claim namedsidcollides with the flattened struct field.refresh.rsdoes no JWT validation (opaque token, store lookup) andexchange.rsdelegates to the provider adapters. The spec states this rather than implying reuse.Drafted with the
spec-creatorplugin from a deep security scan of the repository at53cbdec9. Spec only — no source changes.Resolved: the audit conflict with #24
An earlier revision of this PR flagged a contradiction with its base, #24, and asked for a human decision. It has been settled, and both specs were changed — the resolution is recorded here because the reasoning matters more than the outcome.
The two positions were:
200either way is invisible without a record#24's argument does not survive its own durability model. Its mandatory
SecurityEventchannel ignores thresholds, and underdurability = "enforce"a write failure propagatesErrand the operation fails. Combine that with emitting only on success, and during a sink outage:The asymmetry is the oracle. Silence on the failure branch reintroduces, as degraded-mode behaviour, exactly what §2.2 forbids — and #24 explicitly accepts
enforceas "a deliberate reduction in availability", so a sink outage is an anticipated operating state, not an edge case.RFC 7009 §2.2 constrains the client-visible channel. An audit record is operator-facing; an attacker who can read the sink has already won. Recording a rejection discloses nothing to the caller — provided the emission decision is identical on both branches, which is what keeps the response indistinguishable.
What changed:
03-service-flows.mdRevocation block specifies anAuthenticationFailedevent on the rejection branch and explains why symmetry rather than silence is the control; implementation note 5 no longer exemptsrevokefrom the single-exit wrapper; and a regression test asserts that underenforcewith a failing sink,/revokereturns the same status for a token that exists and one that does not.Noticeseverity was chosen to clearemit_thresholdwhile staying underblocking_threshold; docs(spec): audit and throttle authentication failures #24 removes both levers, so severity no longer carries that argument and the spec now uses docs(spec): audit and throttle authentication failures #24'sAuthenticationFailedclassification.The concern the silence was really carrying — unbounded audit volume from unauthenticated probing — is a sink-cost argument, not an oracle argument, and is handled by the per-IP limiter #24 already introduces.