Skip to content

fix(signing): validate-before-sign symmetry in deliver() + HMAC SSRF coverage + 4.1 migration notes#307

Merged
bokelley merged 1 commit intomainfrom
bokelley/signing-symmetry-followups
Apr 30, 2026
Merged

fix(signing): validate-before-sign symmetry in deliver() + HMAC SSRF coverage + 4.1 migration notes#307
bokelley merged 1 commit intomainfrom
bokelley/signing-symmetry-followups

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

Summary

Three small follow-ups from PR #303's second-pass expert review, plus the migration-guide notes for the signing-prep behavior changes landing in v4.1.

Changes

  1. Validate-before-sign symmetry in webhooks.deliver() — mirrors the WebhookSender._send_bytes pattern from PR feat(signing): close 4 SSRF gaps and add opt-in port hardening (foundation audit) #297. The pinned-transport build (which runs SSRF + port validation) now runs BEFORE body assembly + HMAC computation. A hostile URL raises SSRFValidationError before get_adcp_signed_headers_for_webhook is called, so the HMAC-over-buyer-body never materializes in process memory.

  2. HMAC-SHA256 SSRF coverage — both legacy auth paths (Bearer and HMAC-SHA256) route through the same SSRF guard. The existing test_owned_client_rejects_loopback_destination only exercised Bearer; added test_owned_client_rejects_loopback_destination_hmac_path for parity.

  3. .gitignore — exclude .claude/scheduled_tasks.lock (Conductor harness runtime state).

  4. Migration guideMIGRATION_v4.0_to_v4.1.md gains section chore(main): release 1.0.0 #4 covering the two signing-prep behavior changes adopters need to know about:

    • SSRF guards on owned-client webhook delivery (WebhookSender + deliver()) — dev/CI fixtures posting to private IPs need allow_private=True
    • VerifierCapability.covers_content_digest default flip from \"required\" to \"either\" per AdCP 3.0 spec — adopters who relied on the implicit strict default need to set it explicitly

Test plan

  • pytest tests/test_webhooks_deliver.py — 31 passing (2 new)
  • pytest tests/conformance/signing/ — 422 passing
  • pytest tests/ — 2284 passing locally
  • mypy src/adcp/ — clean
  • ruff check + black --check on changed files — clean
  • CI on 3.10/3.11/3.12/3.13

Semver

Title fix(signing): because:

  • The validate-before-sign reorder is a defense-in-depth correctness fix (no API change)
  • The HMAC test is pure coverage
  • The migration-guide addition is documentation
  • The .gitignore is repo housekeeping

No new public surface, no behavior change beyond what already shipped in PR #297 / #303 (which had feat(signing): for that). release-please should treat this as patch-level on top of 4.1.0.

🤖 Generated with Claude Code

… SSRF coverage

Three small follow-ups from the PR #303 second-pass review (security-reviewer
+ code-reviewer flagged each as low-priority but worth doing for symmetry):

1. validate-before-sign in webhooks.deliver() — mirror WebhookSender ordering.
   The pinned-transport build (which runs SSRF + port validation) now runs
   BEFORE body assembly + HMAC computation. A buyer-supplied 127.0.0.1 URL
   raises SSRFValidationError before get_adcp_signed_headers_for_webhook is
   called, so the HMAC-over-buyer-body never sits in process memory waiting
   for the rejection (anything that snapshots locals on exception cannot
   capture an HMAC that wasn't computed). Matches the
   WebhookSender._send_bytes pattern shipped in PR #297.

   Regression test test_owned_client_rejects_hostile_url_before_hmac_signing
   patches get_adcp_signed_headers_for_webhook with a MagicMock and asserts
   it's never called.

2. HMAC-SHA256 SSRF coverage — the existing
   test_owned_client_rejects_loopback_destination only exercised the Bearer
   auth path. Both auth paths route through the same SSRF guard but the
   tests should cover both for parity. Added
   test_owned_client_rejects_loopback_destination_hmac_path.

3. .gitignore — exclude .claude/scheduled_tasks.lock (Conductor harness
   runtime state).

Plus migration-guide section #4 covering the signing-prep behavior changes
landing in 4.1: SSRF guards on WebhookSender + deliver(), and the
covers_content_digest default flip from "required" to "either" (per AdCP
3.0 spec). Lists the opt-out kwargs adopters who relied on the prior
defaults need to add.

Tests: 2284 passing locally (2 new). Pre-commit clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley bokelley merged commit bc8da3a into main Apr 30, 2026
11 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.

1 participant