Skip to content

adagents.json discovery: follow same-registrable-domain redirects on .well-known, refuse on authoritative_location (adcp#5450) #381

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-go)

Locate the adagents.json / .well-known discovery fetch and apply the policy above.

  • .well-known fetch → follow same-registrable-domain redirects (≤3 hops, per-hop SSRF, anchored on origin), refuse cross-domain. The Go http.Client follows redirects by default — implement a CheckRedirect that enforces the same-domain + HTTPS + hop-cap rules, or follow manually.
  • authoritative_location dereference → set CheckRedirect to refuse (return http.ErrUseLastResponse / an error).
  • PSL private section: golang.org/x/net/publicsuffix does not include private-section control; use github.com/weppos/publicsuffix-go with IncludePrivateDomains: true (or equivalent) so shared-hosting tenants are distinct registrants.

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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