feat(sep-1932): tighten the Bearer-downgrade probe (stacked on #395) - #527
Open
nbarbettini wants to merge 7 commits into
Open
nbarbettini wants to merge 7 commits into
nbarbettini wants to merge 7 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 <noreply@anthropic.com>
- 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 <noreply@anthropic.com>
- 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 <noreply@anthropic.com>
A crash or 404 must not count as correctly refusing a DPoP-bound token presented as Bearer. Co-authored-by: Cursor <cursoragent@cursor.com>
This was referenced Sep 25, 2026
PieterKas
reviewed
Oct 1, 2026
PieterKas
approved these changes
Oct 1, 2026
PieterKas
left a comment
Contributor
There was a problem hiding this comment.
One non-blocking comment for something that Claude (using Fable 5) flagged to prevent a future regression.
PieterKas
added a commit
to PieterKas/conformance
that referenced
this pull request
Oct 1, 2026
Adds sep-1932-server-reject-bearer-downgrade to the traceability yaml, quoting the normative sentence being added to the SEP-1932 extension doc (MCP servers MUST NOT accept a DPoP-bound token presented under the Bearer scheme; rejection is 401 + Bearer/DPoP challenge). The check itself currently runs under sep-1932-server-validate-proof; upstream PR modelcontextprotocol#527 tightens it and is the natural place to re-home it onto this id, so the row links there and reports as untested until then. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Oct 1, 2026
Contributor
|
@nbarbettini SEP-1932 now has a normative sentence for this case (modelcontextprotocol/ext-auth#33) and the conformance yaml in #395 declares sep-1932-server-reject-bearer-downgrade pointing at this PR. Since you're already editing this probe, would you re-home RejectsBearerScheme onto that id (including the error/catch path)? That flips the new traceability row from untested to tested. |
9 tasks
The compliant fixture only challenged with DPoP, so a later edit could drop the Bearer arm of the check and the suite would still pass. Co-authored-by: Cursor <cursoragent@cursor.com>
Keep both the DPoP server scenario registration and the SEP-2640 skills registrations in the pending scenario list. Co-authored-by: Cursor <cursoragent@cursor.com>
SEP-1932 now requires rejecting a DPoP-bound token presented as Bearer, and the traceability row sep-1932-server-reject-bearer-downgrade names that requirement. The result path and the probe-error path both use that id. Co-authored-by: Cursor <cursoragent@cursor.com>
commit: |
Contributor
Author
|
@PieterKas Thanks for your review! I added the additional fixture mode and the traceability note. |
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 #395 (
PieterKas/conformance:dpop-server, headdd9fd77). This branch contains that PR's commits plusdc35b8c. Rebase ontomainonce #395 merges.The new commit (dc35b8c) is the only one to review.
What each check asserts
#395 covers DPoP proof validation on the MCP server. This change only tightens the existing
bearer-schemecase only.sep-1932-server-validate-proof(RejectsBearerScheme)A DPoP-bound token presented under the
Bearerscheme used to pass on any non-2xx. A server that crashes with a 500, or returns a 404, with no challenge was graded as correctly rejecting it.401 with a
WWW-Authenticatechallenge whose scheme isBearer(RFC 6750 §3) orDPoPis SUCCESS. The scheme is matched at the start of the header or after a comma, same ashasDpopChallenge. Any other status, or a 401 with no such challenge, is FAILURE.The compliant fixture still reports SUCCESS.
DPOP_BEARER_REJECT_STATUS=500answers a Bearer-scheme request with that status and no challenge, and reports FAILURE.This case is still filed under
sep-1932-server-validate-proof, whose text is "MCP servers MUST validate DPoP proofs according to RFC 9449 Section 4.3". Refusing a bound token presented as Bearer is not a §4.3 step, and RFC 9449 §7.2 is descriptive only ("would most presumably accept"). SEP-1932 needs a normative sentence for this, and the check may deserve its own id once that sentence exists. No yaml text was added.Testing
npm test— 46 files / 595 tests passnpm run lint(eslint + prettier) — cleanMade with Cursor