Skip to content

feat(training-agent): add /mcp-strict-required and /mcp-strict-forbidden conformance routes#3340

Merged
bokelley merged 1 commit into
mainfrom
claude/issue-3339-mcp-strict-required-forbidden-routes
Apr 27, 2026
Merged

feat(training-agent): add /mcp-strict-required and /mcp-strict-forbidden conformance routes#3340
bokelley merged 1 commit into
mainfrom
claude/issue-3339-mcp-strict-required-forbidden-routes

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

Closes #3339

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' — 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 enforces covers_content_digest: 'required'. Rejects signatures that omit content-digest coverage → fires neg/007.
  • /mcp-strict-forbidden — advertises and enforces covers_content_digest: 'forbidden'. Rejects signatures that include content-digest coverage → fires neg/018.

/mcp-strict is unchanged (covers_content_digest: 'either').

Implementation: the internal buildStrictAuthenticator is refactored into a buildStrictModeAuthenticator(lazyAuth) factory so each route holds its own Authenticator instance with its capability baked at init time (per-request capability swap is unsafe — verifySignatureAsAuthenticator captures capability at construction). digestMode is added to TrainingContext so selectSigningCapability(ctx) can pick the right capability for both the verifier and the get_adcp_capabilities advertisement.

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:

  • code-reviewer: approved — both blockers (resetRequestSigning incomplete, no integration tests) fixed; nit (hermetic beforeAll) addressed by adding resetRequestSigning() call
  • ad-tech-protocol-expert: approved — route-per-mode pattern is correct; required_for: ['create_media_buy'] on new routes is sound for existing vector set; non-breaking confirmed

Triage-managed PR. This bot does not currently iterate on
review comments or PR conversation threads (only on the source
issue). To unblock:

  • Push fixup commits directly: gh pr checkout <num>
    fix → push.
  • Or re-trigger: comment /triage execute on the source
    issue.

See #3121
for context.

Session: https://claude.ai/code/session_01RX1tXNkZQXs6TaZEFKdb2d


Generated by Claude Code

…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
@bokelley bokelley force-pushed the claude/issue-3339-mcp-strict-required-forbidden-routes branch from ebc8b4e to 5585d8b Compare April 27, 2026 11:08
@bokelley bokelley marked this pull request as ready for review April 27, 2026 11:12
@bokelley bokelley merged commit 051ef24 into main Apr 27, 2026
15 checks passed
@bokelley bokelley deleted the claude/issue-3339-mcp-strict-required-forbidden-routes branch April 27, 2026 11:12
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

claude-triaged Issue has been triaged by the Claude Code triage routine. Remove to re-triage.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

training-agent verifier: covers_content_digest='either' fails grader neg/007 + neg/018 — clarify grader vs. capability semantics

2 participants