You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Second-pass expert review (security-reviewer, code-reviewer,
python-expert, ad-tech-protocol-expert) on the scope-down commit
3fd2c49 surfaced 1 ship-blocker, 2 real bugs, 3 missing tests, and a
nit. All addressed.
Ship-blocker (security-reviewer):
1. WebhookSender's per-request httpx.AsyncClient missed trust_env=False.
httpx defaults trust_env=True, which routes the signed webhook
through any HTTPS_PROXY / HTTP_PROXY env var, bypassing the
AsyncIpPinnedTransport entirely. Every other pinned-transport
callsite in this codebase explicitly sets trust_env=False
(default_jwks_fetcher, async_default_jwks_fetcher,
revocation_fetcher); the webhook sender was the outlier. An
attacker who controls process env (sidecar config, dotenv,
malicious cluster egress policy) could otherwise pivot to receiving
the signed webhook body. One-line fix at webhook_sender.py:577 with
a regression test that asserts the kwarg is set on the per-request
client.
Bugs (python-expert):
2. Stale docstrings in build_ip_pinned_transport and
build_async_ip_pinned_transport claimed allowed_ports defaults to
DEFAULT_ALLOWED_PORTS ({443, 8443}) — but the scope-down flipped
the default to None (no port filter). Adopters reading the
docstring would hit confusing rejections. Updated both to describe
the actual behavior.
3. _send_bytes signed the body before SSRF-validating the URL.
Restructured so the pinned-transport build (which runs SSRF + port
validation) happens first; signing only after validation succeeds.
Hostile URLs no longer leave a signed payload in process memory
for faulthandler / custom logging hooks to capture on exception.
New regression tests (code-reviewer + security-reviewer):
4. test_owned_client_default_allows_non_standard_ports — sender-level
positive analog of the validator-level test_ssrf_default_imposes_no_port_filter.
Confirms the permissive port default reaches the actual delivery
path; AdCP-spec-compliant buyers on :9443 (Tomcat) and similar
non-standard ports succeed without explicit allowlist.
5. test_operator_supplied_client_bypasses_ssrf_guard — named regression
guard for the documented contract. Without this, a future refactor
that mistakenly applies pin-and-bind to both branches would break
ASGI-based unit tests and any vetted-egress-proxy deployment that
routes via private networks.
6. test_owned_client_ignores_https_proxy_env — regression guard for
trust_env=False. Patches HTTPS_PROXY in env, asserts the per-request
client constructs with trust_env=False so the proxy is ignored.
Code-reviewer nit:
7. Deduplicated DEFAULT_ALLOWED_PORTS rationale block-comment between
adcp.signing.jwks (constant definition) and tests/conformance/signing/test_jwks.py.
Kept at the constant-definition site; test file points to it.
Commit type changed from fix(signing) to feat(signing):
The PR adds public surface (DEFAULT_ALLOWED_PORTS export, new kwargs
on validate_jwks_uri / resolve_and_validate_host / build_*_pinned_transport /
WebhookSender / from_jwk / from_pem) and changes WebhookSender._send_bytes
behavior on the owned-client path (now SSRF-validates and pin-binds
every delivery). Per semver, additive public-API surface = minor;
the security-fix-via-strictening-default is also conventionally a
minor bump. release-please should tag this as 4.1.0, not 4.0.1.
If squash-merging, the maintainer should use a feat(signing): PR title
so the squash subject carries the conventional-commit type that
release-please reads.
Tests: 2257 passing locally (3 new). Pre-commit clean (black, ruff,
mypy, bandit).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0 commit comments