fix(adagents): follow safe well-known redirects#967
Merged
Conversation
Contributor
There was a problem hiding this comment.
Approving. Redirect-following is re-implemented above the follow_redirects=False primitive, so every hop re-clears the same SSRF gate the old no-follow policy relied on — the boundary moved, the invariant didn't.
Things I checked
- SSRF holds on every followed hop.
_fetch_well_known_adagents_urlsetscurrent_client = Noneafter the first response, so hops ≥1 build a fresh_owned_pinned_client(IP-pinned,trust_env=False, adagents.py:272-306) and_resolve_well_known_redirect_urlre-runs_check_safe_host+_dns_validate_hostbefore any bytes. The caller-injected (possibly unpinned) client only ever touches the original publisher URL — no regression. - eTLD+1 anchor is correct against PSL private abuse. Both sides ACE-normalized via
_idna_ascii_host(uts46=True)beforesame_registrable_domain(etld.py:113), which setsinclude_psl_private_domains=True— sovictim.github.io→attacker.github.iois rejected.original_hostnameis held constant across hops (anchored to origin, not chained). IDN test (bücher.com→xn--bcher-kva.com) passes. authoritative_locationstays strictly no-follow._resolve_directroutesis_redirect=Trueto_fetch_adagents_url(no-follow); a 30x on the named authoritative URL falls tostatus_code != 200and raisesHTTP {status}. Confirmed bytest_authoritative_location_http_redirect_is_refused(test_adagents.py:896,match=\"HTTP 302\").- Hop math.
range(MAX_WELL_KNOWN_REDIRECT_HOPS + 1)= 4 iterations, raises athop >= 3→ at most 3 redirects (4 fetches). Circular detection keys on the resolved absolute URL per iteration. Cap test and circular test both trace correctly. - Non-redirect path equivalence. The
_fetch_adagents_url→_adagents_headers+_fetch_adagents_response+_parse_adagents_responsesplit carries every old inline branch (304/404/403-cf-challenge/non-200/JSON-object/authorized_agents-vs-authoritative_location) over verbatim. The validator reset (_adagents_headers(user_agent, None)+current_cache = None) on redirect hops correctly fail-closes via the existing "304 without a cache entry" guard. - Deps.
idna>=3.7,<4declared inpyproject.toml— the>=3.7floor excludes CVE-2024-3651, which matters now that attacker-controlled redirect hostnames flow intoidna.encode. Load-bearing; don't relax it.
Follow-ups (non-blocking — file as issues)
- Spec is silent on well-known redirects.
ad-tech-protocol-expert: sound-with-caveats — AdCP doesn't normatively say the well-known fetch is no-redirect, nor mandate apex→www normalization, so this is a defensible unilateral SDK policy rather than a spec requirement. Worth an upstream clarification so a publisher whose apex 301s to www resolves the same on a strict implementation (e.g. the JS SDK) as it does here. - Independent
visited_urlssets.security-reviewer(Low): the well-known loop's cycle set is separate from_resolve_direct's. Not exploitable — both chains are independently hop-capped (3 andMAX_REDIRECT_DEPTH=5) — but one shared set would tighten posture.
Minor nits (non-blocking)
- Bounded non-repeating bounce. The circular check is exact-string on the
urljoin-normalized URL, so a server can still bounce/a→/b→/a?x=1without repeating. Fully bounded by the hop cap, so it's cosmetic — noting for completeness.
security-reviewer: no blockers. code-reviewer: no Blocker/Major. ad-tech-protocol-expert: sound-with-caveats. LGTM. Follow-ups noted below.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #947.
This updates the initial
/.well-known/adagents.jsondiscovery fetch to follow only safe HTTP redirects:Locationtargetssame_registrable_domainhelperauthoritative_locationdereferences remain no-follow; a 30x response from that exact named URL is still surfaced as an error.Validation
PYTHONPATH=src pytest tests/test_adagents.py -qPYTHONPATH=src pytest tests/test_adagents.py::TestFetchAdagents tests/test_adagents.py::TestSSRFProtection tests/test_etld.py -quv run --python 3.11 --extra dev ruff check src/adcp/adagents.py tests/test_adagents.pyExpert review
authoritative_locationno-follow behavior.Locationand circular redirect coverage, both added.