Skip to content

Wire SSRF guard into legacy webhooks.deliver() #299

@bokelley

Description

@bokelley

Summary

adcp.webhooks.deliver() (the deprecated AdCP 3.x HMAC-SHA256 path) constructs an unpinned httpx.AsyncClient(timeout=...) and POSTs to a buyer-controlled URL when no operator client is supplied. No SSRF guard.

Source: src/adcp/webhooks.py:867-872. Surfaced by security-reviewer on PR #297 as L4 — explicitly deferred from the prep PR scope.

Why deferred

deliver() is the AdCP 3.x deprecated path; emits DeprecationWarning already; new traffic goes through WebhookSender. PR #297 was scoped to RFC 9421 sender hardening.

Proposed fix

Mirror the WebhookSender._send_bytes pattern:

  • When the operator supplies a client, trust them.
  • When the sender owns the client, build a per-request AsyncIpPinnedTransport via build_async_ip_pinned_transport(url, ...) and construct httpx.AsyncClient(transport=transport, follow_redirects=False, trust_env=False).
  • Same allow_private_destinations and allowed_destination_ports kwargs on the public surface.

Risk

Adopters on the legacy deliver() path posting to private/internal endpoints (dev/test fixtures) will start getting SSRFValidationError. One-kwarg opt-out via allow_private=True (or whatever name fits the existing deliver() surface).

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions