Skip to content

spec(webhooks): fix vectors 004/005 to apply full @target-uri canonicalization#2470

Merged
bokelley merged 2 commits into
mainfrom
bokelley/fix-webhook-vectors
Apr 20, 2026
Merged

spec(webhooks): fix vectors 004/005 to apply full @target-uri canonicalization#2470
bokelley merged 2 commits into
mainfrom
bokelley/fix-webhook-vectors

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

Summary

  • Webhook-signing positive vectors 004 and 005 shipped with signatures that only verified if the signer skipped @target-uri canonicalization steps 4 (strip default ports) and 6 (uppercase %xx / decode percent-encoded unreserved). The shared rules in request-signing/canonicalization.json mandate both, so the two vector sets contradicted each other.
  • Vector 004: signature base now uses the canonical URI with :443 stripped (input URL unchanged).
  • Vector 005: input URL changed from op%2dabc (%2d is unreserved -, which step 6 requires to decode — the old example overloaded "uppercase hex" with "decode unreserved") to op_%e2%98%83 (reserved UTF-8 bytes, same pattern as request-signing/positive/008). Canonical @target-uri uppercases to %E2%98%83.
  • Both Ed25519 signatures regenerated deterministically and verified against the published test-ed25519-webhook-2026 keypair.

Implementor note

Any verifier that previously passed the old 004/005 has a latent canonicalization bug: it accepted signatures produced over the as-received URL rather than the canonical @target-uri. That verifier will silently disagree with a correctly-canonicalizing producer on any URL containing :443, :80, or lowercase %xx in the path, causing webhook_signature_invalid at step 10. Re-run the positive suite after pulling.

Test plan

  • Precommit: npm run test:unit (587/587) + npm run typecheck passing locally
  • Regenerated Ed25519 signatures verified against test-ed25519-webhook-2026 public key
  • Confirmed untouched positive vectors (001, 002, 003, 006, 007) have canonical == as-received URLs — no latent canonicalization bug in the remaining five
  • Confirmed no negative vector references :443, %2d, or lowercase-hex path encoding
  • CI passes on PR

🤖 Generated with Claude Code

bokelley and others added 2 commits April 19, 2026 22:56
…alization

Positive webhook-signing vectors 004-default-port-stripped and
005-percent-encoded-path shipped with signatures that only verified if the
signer skipped canonicalization steps 4 (strip default ports) and 6 (uppercase
%xx / decode percent-encoded unreserved). The shared rules in
request-signing/canonicalization.json mandate both, so the two sets
contradicted each other.

- 004: signature base now uses canonical @target-uri without :443.
- 005: input URL changed from op%2dabc (%2d is unreserved '-', which step 6
  requires to decode — the old example overloaded "uppercase hex" with
  "decode unreserved") to op_%e2%98%83 (reserved UTF-8 bytes, same pattern as
  request-signing positive/008). Canonical @target-uri uppercases to %E2%98%83.

Both Ed25519 signatures regenerated and verified against
test-ed25519-webhook-2026.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Empty frontmatter silently no-ops during version. Populate per the project
convention for published-spec changes (the test vectors ship at
adcontextprotocol.org/test-vectors/).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley bokelley merged commit fa3835c into main Apr 20, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants