feat(sep-1932): DPoP error codes (stacked on #527) - #528
Open
nbarbettini wants to merge 5 commits into
Open
nbarbettini wants to merge 5 commits into
nbarbettini wants to merge 5 commits into
Conversation
…ocol#369) Follow-up on the DPoP client PR (shared foundation). Adds the server-role conformance for SEP-1932 / RFC 9449: the framework acts as a DPoP client against the MCP server under test and emits the sep-1932-server-* checks across the RFC 9449 §4.3 validation surface — proof validation, the ±5-minute iat window, asymmetric-only algorithms, the 401 + WWW-Authenticate challenge, token audience validation under DPoP, and the optional server-provided nonce. - src/scenarios/server/auth/dpop.ts (+ test, spec-references): the scenario. - examples/servers/typescript/sep-1932-{compliant,broken}-server.ts: passing and failing fixtures proving every check passes and fails. - Registered in the pending + all-client scenario lists. Depends only on the shared DPoP foundation (dpopProof/dpopToken); independent of the authorization-server PR. No DPoP-capable MCP SDK exists yet, so correctness rests on RFC vectors + an independent verifier + the fixtures. Co-Authored-By: Claude Opus 4.8 <[email protected]>
- Refresh the held DPoP nonce from every response's DPoP-Nonce header (RFC 9449 §8.2 newest-wins) instead of capturing it once, so a server that rotates or single-uses its nonce no longer turns the negative probes into false "not testable" failures (or vacuous passes). (modelcontextprotocol#1) - Widen the stale iat probe to -303 s (matching the future side) so the ±1 s quantization of the whole-second Date header can't pull it onto the ±300 s boundary and be false-accepted. (modelcontextprotocol#2) - Update the isNonceChallenge comment and the untestable message: probes now carry the held nonce, so a use_dpop_nonce challenge is about the nonce lifetime (rotated/stale/single-use), not a missing nonce. (modelcontextprotocol#3) - Fixture: parse integer env vars NaN-safely (intEnv helper) so a malformed DPOP_CLOCK_OFFSET_SECONDS / DPOP_IAT_SKEW_SECONDS falls back to its default instead of silently disabling the iat window. (modelcontextprotocol#4) Co-Authored-By: Claude Opus 4.8 <[email protected]>
- Soften the heldNonce comment: newest-wins refresh is correct for ROTATING servers; a strict single-use server that re-arms only via challenges can still push alternate negatives to untestable (correctly reported, not mis-scored) — the previous "rotate or single-use" over-claimed. (R5) - Fix the clock-skew test comment: the stale probe is now -303s (~273s old), not -301s/~271s. (R5) Comment-only; no behavior change. Co-Authored-By: Claude Opus 4.8 <[email protected]>
A crash or 404 must not count as correctly refusing a DPoP-bound token presented as Bearer. Co-authored-by: Cursor <[email protected]>
A challenge with no error code was passing. The code is a SHOULD, so a wrong or missing value is a warning and the existing MUST checks stay as they are. Co-authored-by: Cursor <[email protected]>
nbarbettini
force-pushed
the
feat/1932-server-error-code-get
branch
from
September 25, 2026 17:35
a727a7b to
9771ecb
Compare
This branch has not been deployed
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.
Stacked on #527 -> #395 (
PieterKas/conformance:dpop-server, headdd9fd77). This branch contains those commits plus9771ecb. Rebase onto #395 once #527 merges.The new commit (9771ecb) is the only one to review here.
What each check asserts
#395 checks that a malformed proof is rejected with a DPoP challenge. It does not check which error the server reports. #527 tightens the Bearer-scheme probe so only a 401 with a Bearer or DPoP challenge counts.
sep-1932-server-error-codeRFC 9449 §7.1, graded SHOULD from "An
errorparameter ([RFC6750], Section 3) SHOULD be included to indicate the reason why the request was declined, if the request included an access token but failed authentication. The error parameter values described in [RFC6750], Section 3.1 are suitable, as are any appropriate values defined by extension. The valueuse_dpop_noncecan be used as described in Section 9 to signal that a nonce is needed in the DPoP proof of a subsequent request(s). Additionally,invalid_dpop_proofis used to indicate that the DPoP proof itself was deemed invalid based on the criteria of Section 4.3."One check. The
errorauth-param is read from the DPoP challenge (the header may carry several), quoted or unquoted. Mismatches are listed indetails. Absent or unexpected is WARNING. SUCCESS otherwise. Gated on the positive baseline. Existing MUST checks do not look at the code, so their status is unchanged.invalid_dpop_proofinvalid_tokenDPOP_ERROR_CODE_OVERRIDE=invalid_requestreturns that code on every 401. The check reports WARNING. The compliant fixture already emitsinvalid_dpop_prooffor proof defects andinvalid_tokenfor token defects.GET is not probed. Revision 2026-07-28 removed the GET stream, and a server that speaks only this revision SHOULD answer GET with 405, so a GET cannot show whether
htmwas checked.htmstays covered by the existing POST probe (RejectsHtmMismatch, proofhtmof GET).Testing
DPOP_ERROR_CODE_OVERRIDEMade with Cursor