feat(training-agent): add /mcp-strict-required and /mcp-strict-forbidden conformance routes#3340
Merged
bokelley merged 1 commit intoApr 27, 2026
Conversation
…den conformance routes Grader vectors neg/007 (missing-content-digest) and neg/018 (digest-covered-when-forbidden) can't fire against /mcp-strict because it advertises covers_content_digest='either'. Adds two dedicated grader-target routes that advertise and enforce 'required' and 'forbidden' modes respectively, giving buyers an endpoint to smoke-test required-mode and forbidden-mode rejection paths in their signing implementations. Closes #3339 https://claude.ai/code/session_01RX1tXNkZQXs6TaZEFKdb2d
ebc8b4e to
5585d8b
Compare
3 tasks
bokelley
added a commit
that referenced
this pull request
Apr 27, 2026
The post-5.21.1 grader run surfaced neg/016-replayed-nonce accepting both submissions of the same (keyid, nonce) pair on /mcp-strict — a MUST-level RFC 9421 §3.3.2 violation. Root cause: /mcp-strict was using the same lazySigningAuth() singleton as /mcp, so they shared one InMemoryReplayStore. The shared singleton was also bound to the *default* capability (required_for: []) rather than the strict one (required_for: ['create_media_buy']) — a quieter conformance gap that compounded with the replay leak. Adds buildStrictRequestSigningAuthenticator() in request-signing.ts (parallel to the existing strict-required and strict-forbidden builders from #3340), and a matching lazyStrictSigningAuth() in index.ts. /mcp-strict now binds to its own replay store and the strict capability. Un-skips the regression test at training-agent-strict.test.ts:124 (was skipped per #3080 with a stale assertion); regex updated to match the SDK's current "Signature required for create_media_buy." text. The triage's bug #1 ("bearer evaluated before signing") didn't reproduce against @adcp/client@5.21.1 — requireSignatureWhenPresent already implements presence-first ordering. Per-route signing-auth instances eliminate any leftover bypass surface regardless. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley
added a commit
that referenced
this pull request
Apr 27, 2026
The post-5.21.1 grader run surfaced neg/016-replayed-nonce accepting both submissions of the same (keyid, nonce) pair on /mcp-strict — a MUST-level RFC 9421 §3.3.2 violation. Root cause: /mcp-strict was using the same lazySigningAuth() singleton as /mcp, so they shared one InMemoryReplayStore. The shared singleton was also bound to the *default* capability (required_for: []) rather than the strict one (required_for: ['create_media_buy']) — a quieter conformance gap that compounded with the replay leak. Adds buildStrictRequestSigningAuthenticator() in request-signing.ts (parallel to the existing strict-required and strict-forbidden builders from #3340), and a matching lazyStrictSigningAuth() in index.ts. /mcp-strict now binds to its own replay store and the strict capability. Un-skips the regression test at training-agent-strict.test.ts:124 (was skipped per #3080 with a stale assertion); regex updated to match the SDK's current "Signature required for create_media_buy." text. The triage's bug #1 ("bearer evaluated before signing") didn't reproduce against @adcp/client@5.21.1 — requireSignatureWhenPresent already implements presence-first ordering. Per-route signing-auth instances eliminate any leftover bypass surface regardless. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 tasks
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.
Closes #3339
Grader vectors neg/007 (
missing-content-digest) and neg/018 (digest-covered-when-forbidden) can't fire against/mcp-strictbecause it advertisescovers_content_digest='either'— correct for the sandbox, but it means the verifier accepts both probe shapes and the negative paths are silently untestable. Buyers smoke-testing their signing implementation have no endpoint to verify their code handles required-mode and forbidden-mode rejection paths.This PR adds two dedicated grader-target routes:
/mcp-strict-required— advertises and enforcescovers_content_digest: 'required'. Rejects signatures that omit content-digest coverage → fires neg/007./mcp-strict-forbidden— advertises and enforcescovers_content_digest: 'forbidden'. Rejects signatures that include content-digest coverage → fires neg/018./mcp-strictis unchanged (covers_content_digest: 'either').Implementation: the internal
buildStrictAuthenticatoris refactored into abuildStrictModeAuthenticator(lazyAuth)factory so each route holds its ownAuthenticatorinstance with its capability baked at init time (per-request capability swap is unsafe —verifySignatureAsAuthenticatorcaptures capability at construction).digestModeis added toTrainingContextsoselectSigningCapability(ctx)can pick the right capability for both the verifier and theget_adcp_capabilitiesadvertisement.Note on the grader bug (Option 1 from the issue): the grader's
black_box (contract loaded)harness should ideally skip neg/007 and neg/018 when the verifier under test advertises'either'— firing them unconditionally is a grader bug per the AdCP spec's capability-gating rule. That fix belongs in adcp-client and should be tracked as a follow-on issue there.Non-breaking justification: adds new routes, new capability getter functions, and a new optional field on
TrainingContext. No existing routes, schemas, or API contracts are changed. Existing consumers are unaffected.Pre-PR review:
resetRequestSigningincomplete, no integration tests) fixed; nit (hermeticbeforeAll) addressed by addingresetRequestSigning()callrequired_for: ['create_media_buy']on new routes is sound for existing vector set; non-breaking confirmedSession: https://claude.ai/code/session_01RX1tXNkZQXs6TaZEFKdb2d
Generated by Claude Code