Skip to content

adagents.json discovery: replace follow_redirects=False with same-registrable-domain following (apex→www); refuse on authoritative_location (adcp#5450) #947

@bokelley

Description

@bokelley

Context

AdCP issue adcontextprotocol/adcp#5450 and merged spec PR adcontextprotocol/adcp#5472 define the redirect policy for adagents.json discovery. The reference validator in adcontextprotocol/adcp is now aligned; this SDK needs the same behavior so all AdCP implementations resolve discovery identically.

The bug this fixes: a publisher whose apex domain 301-redirects to www (the default on most managed hosting / CDNs) is silently reported unauthorized by validators that refuse all redirects on the discovery fetch.

Policy — two hops, deliberately different

Fetch Redirect policy
Initial https://{domain}/.well-known/adagents.json Follow same-registrable-domain redirects (apex↔www, HTTPS-preserving, ≤3 hops), re-validating SSRF (reject non-HTTPS, reject reserved/private IPs, pin connect) on every hop, with the same-domain comparison anchored on the originally-requested domain at every hop (not the previous hop). Refuse cross-registrable-domain redirects — that is an unscoped delegation signal; the publisher should declare delegation via authoritative_location instead.
authoritative_location dereference (2nd hop) Refuse all redirects. The named URL is the declared authoritative location; a redirect away from it changes that declaration and MUST be treated as an error.

⚠️ Security-critical: use the PSL private section

Registrable-domain comparison MUST use the Public Suffix List including its PRIVATE section. Without it, shared-hosting tenants collapse to one registrable domain — victim.github.io and attacker.github.io both reduce to github.io — and a cross-tenant redirect would be wrongly followed and trusted as authoritative. The same applies to *.pages.dev, *.web.app, *.herokuapp.com, *.wordpress.com, etc. The reference impl uses tldts.getDomain(host, { allowPrivateDomains: true }).

Conformance

Gate on the shared cross-SDK vectors: static/test-vectors/adagents-discovery-redirects.json. They cover apex↔www, subdomain, co.uk (ICANN multi-label), *.github.io (PSL private section), same→cross two-hop (origin-anchored), HTTPS downgrade, hop cap, and the authoritative-location refuse-all rule. The normative assertion per vector is result (resolved vs refused).

This SDK (adcp-client-python)

Per the #5450 report, the .well-known/adagents.json fetch currently passes follow_redirects=False (in adcp/adagents.py), which is exactly what breaks apex→www publishers.

  • .well-known fetch → replace follow_redirects=False with a manual same-registrable-domain redirect loop: follow only same-eTLD+1, HTTPS-preserving, ≤3 hops, re-validating SSRF on each hop, anchored on the originally-requested domain; refuse cross-domain.
  • authoritative_location dereference → keep redirects refused.
  • PSL with private domains: e.g. tldextract with include_psl_private_domains=True, or publicsuffixlist/publicsuffix2 with private rules — so victim.github.ioattacker.github.io.

Acceptance: the discovery path passes every vector in adagents-discovery-redirects.json.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions