Skip to content

spec(security): bundle the Public Suffix List; AdCP names no snapshot (DR-0037) - #8702

Open
bokelley wants to merge 4 commits into
mainfrom
bokelley/8409-psl-requirement
Open

bokelley wants to merge 4 commits into
mainfrom
bokelley/8409-psl-requirement

Conversation

@bokelley

@bokelley bokelley commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Closes #8409, taking @KonstantinMirin's proposal from that thread: drop the cross-verifier PSL pinning requirement instead of building infrastructure for it.

Decision (DR-0037)

  • Keep: ICANN + PRIVATE sections, the list bundled with the verifier, never fetched at runtime, and fail closed when a host has no eTLD+1.
  • Remove: "two verifiers running different PSL versions are non-conformant against each other", "pinned, dated snapshot" in R3a step 1, and the library names in the brand_json_url quickstart (the Python one was wrong anyway).
  • Add: verifiers refresh the list through normal dependency updates. AdCP names no snapshot.

Why: lists differ only in suffixes added or removed between them, mostly in the PRIVATE section. On the trust-record path, a verifier with an older list derives the platform's trust record, which doesn't list the tenant, so the claim fails closed. The brand_json_url origin binding goes the other way: an older list gives two tenants of a newly listed platform one eTLD+1 until the verifier updates. That's how every verifier behaved before the listing, and a pinned snapshot would keep the gap open until the next release. A shared snapshot also makes every verifier equally stale for a release cycle and costs a vendored list, SDK changes, vectors, and upkeep.

Changes

  • security.mdx: step 3 of Discovering an agent's signing keys is the one home for the rule. The quickstart step 3 and Web Bot Auth identities step 1 now point to it.
  • specs/agent-identity-3-3.md: R3a step 1 wording and the "PSL drift" threat row.
  • L2/accounts-and-agents.mdx and the trust.json grantee description (description only): "pinned snapshot" becomes "Public Suffix List".
  • governance/decisions/DR-0037: amends DR-0023 in part (R3a's "pinned, dated" wording), and DR-0023 carries the Amended by DR-0037 note.
  • brand-identity-trust-split.md origin binding, and the quickstart sample (no PINNED_PSL_SNAPSHOT).

This relaxes a requirement, so every existing implementation stays conformant; an SDK that pins a snapshot still conforms. After merge, the SDK tracking issues adcontextprotocol/adcp-client#3379, adcontextprotocol/adcp-client-python#1562 and adcontextprotocol/adcp-go#590 close with no code changes.

🤖 Generated with Claude Code

… (DR-0037)

Resolves #8409 with Konstantin's proposal: keep ICANN+PRIVATE, no runtime
fetch, and fail closed with no eTLD+1; drop "two verifiers running
different PSL versions are non-conformant against each other" and the
library names; refresh through dependency updates. Step 3 of Discovering
an agent's signing keys is the one home, and R3a step 1, L2, the threat
table, and the trust.json grantee description point to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Escalate to human review

Escalate — gated path touched without required approval, plus a Breaking-class governance question.

This PR relaxes the cross-verifier PSL pinning requirement and ratifies DR-0037. The diff modifies static/schemas/source/trust/v1/trust.json, which is under the hard, path-based approval gate (gated_paths: true). The current review_decision is REVIEW_REQUIRED, not APPROVED, so the gate is NOT satisfied and the outcome must be escalate (decision table row 2).

Additionally, this is a normative spec relaxation that reverses a previously-stated requirement — a governance/ratification decision that per the repo's "ratification is a human act" rule is never auto-approved without review_decision: APPROVED. Both triggers point to the same place: a human/CODEOWNERS reviewer must sign off.

Medium findings

  • specs/capabilities-brand-url.md:384 — Sibling design specs (capabilities-brand-url.md:325,384; brand-identity-trust-split.md:149) still assert the "pinned PSL snapshot" requirement DR-0037 now reverses. Coherence follow-up; not blocking on its own.

No critical/high findings: schema change is description-only and coherent with L2 docs, no oneOf/wire-shape change, changeset present and correctly scoped. The remaining concern is purely the approval gate.

Escalation reasons

  • static/schemas/source/trust/v1/trust.json is under the gated-paths approval gate and review_decision is REVIEW_REQUIRED — human/CODEOWNERS review required before merge.
  • Normative spec relaxation reversing a prior requirement (DR-0037) — ratification is a human act.

Medium findings

  • specs/capabilities-brand-url.md:384 — Sibling design specs still assert the "pinned PSL snapshot" requirement DR-0037 reverses

Why human review

  • static/schemas/source/trust/v1/trust.json is under the gated-paths hard approval gate and review_decision is REVIEW_REQUIRED (not APPROVED) — human/CODEOWNERS review required
  • Normative spec relaxation reversing a prior requirement and ratifying DR-0037 — ratification is a human act, not auto-approvable
  • This PR touches a path under a hard, non-overridable approval gate (static/schemas/source/trust/v1/trust.json (modified) matches static/schemas/source/**) and the current GitHub review decision is 'REVIEW_REQUIRED', not APPROVED. This is a hard gate enforced in code — Ladon cannot auto-approve until a human/CODEOWNERS approval is recorded, regardless of how clean the diff is.

@aao-secretariat aao-secretariat Bot added the ladon/needs-human-review Ladon has escalated this PR for human review. label Nov 2, 2026

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Escalate to human review

Escalate — gated path touched without required approval.

This PR modifies static/schemas/source/trust/v1/trust.json, which matches the deterministic hard approval gate (static/schemas/source/**). gated_paths is true and review_decision is REVIEW_REQUIRED (not APPROVED), so per decision rule row 2 the outcome must be escalate — human/CODEOWNERS approval is required before this can merge.

No blocking code findings: the reviewer found a clean, coherent spec/governance change dropping the cross-verifier PSL-pinning requirement in favor of a bundled, never-fetched-at-runtime list, ratified in new record DR-0037. Changeset is present and correctly typed patch (trust.json is experimental, the change only relaxes a conformance requirement, a pinned snapshot still conforms); the schema edit is description-only (no wire-shape change, so no schema↔docs drift); DR-0037's partial supersession of DR-0023 is accurate; all live normative surfaces updated consistently; fail-closed reasoning sound and the runtime-fetch ban retained.

The change itself is non-breaking (a relaxation, not a breaking wire change), but the gated-path approval gate is unsatisfied regardless. A real human/CODEOWNERS approval lifts this gate.

Escalation reasons:

  • static/schemas/source/trust/v1/trust.json is under the hard approval gate (static/schemas/source/**) and review_decision is REVIEW_REQUIRED, not APPROVED — human/CODEOWNERS review required.

Why human review

  • Modifies static/schemas/source/trust/v1/trust.json (gated path static/schemas/source/**) while review_decision is REVIEW_REQUIRED, not APPROVED — human/CODEOWNERS approval required to satisfy the hard approval gate.
  • This PR touches a path under a hard, non-overridable approval gate (static/schemas/source/trust/v1/trust.json (modified) matches static/schemas/source/**) and the current GitHub review decision is 'REVIEW_REQUIRED', not APPROVED. This is a hard gate enforced in code — Ladon cannot auto-approve until a human/CODEOWNERS approval is recorded, regardless of how clean the diff is.

Ladon's coherence finding on #8702. capabilities-brand-url.md's revision
log stays as history.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

bokelley commented Nov 3, 2026

Copy link
Copy Markdown
Contributor Author

@KonstantinMirin, this is your #8409 proposal, recorded as DR-0037. Could you review and approve? It needs a human approval: the description-only edit to trust.json falls under the schema path gate, and the PR ratifies a decision record. Ladon found nothing blocking, and its one coherence finding (the brand-identity-trust-split.md origin-binding line) is fixed in e5cd3e8. The capabilities-brand-url.md revision-log entry stays as history.

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Escalate to human review

Escalate — hard approval gate not yet satisfied.

This PR modifies static/schemas/source/trust/v1/trust.json, which matches the gated path glob static/schemas/source/**. Per the deterministic hard approval gate (decision rule row 2), gated_paths: true AND review_decision: REVIEW_REQUIRED (not APPROVED) forces escalation — human/CODEOWNERS approval is required before this can merge, regardless of how clean the diff is.

The reviewer's delta is clean: 29 lines across 2 files, both coherence cleanups for DR-0037 (PSL bundled, not pinned). DR-0037 frontmatter fills in the merged PR number (metadata-only); brand-identity-trust-split.md:146 aligns the last "pinned PSL snapshot" straggler to the "bundled with the verifier, never fetched at runtime" wording and cites DR-0037. No wire-shape change, no schema↔docs drift, no changeset-scope issue, no contradiction of governance precedent. No Critical/High/Medium findings.

This is a sticky escalation consistent with the prior decision: the gated-path hard gate remains unsatisfied. Once a human/CODEOWNERS approval is recorded (review_decision → APPROVED), this clean diff can fall through to approve.

Escalation reasons

  • Modifies static/schemas/source/trust/v1/trust.json (gated path static/schemas/source/**) while review_decision is REVIEW_REQUIRED, not APPROVED — human/CODEOWNERS approval required to satisfy the hard approval gate.

Why human review

  • Modifies static/schemas/source/trust/v1/trust.json (gated path static/schemas/source/**) while review_decision is REVIEW_REQUIRED, not APPROVED — human/CODEOWNERS approval required to satisfy the hard approval gate.
  • This PR touches a path under a hard, non-overridable approval gate (static/schemas/source/trust/v1/trust.json (modified) matches static/schemas/source/**) and the current GitHub review decision is 'REVIEW_REQUIRED', not APPROVED. This is a hard gate enforced in code — Ladon cannot auto-approve until a human/CODEOWNERS approval is recorded, regardless of how clean the diff is.

@KonstantinMirin KonstantinMirin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Brian,

Thanks for picking this up. The decision reads right, and the R3a, step 3, and trust.json edits match what I proposed in #8409. Two places still say "pinned", and the record overstates three things. Each fix is a line or two.

Still says pinned

  1. security.mdx:1454-1455. The verifier quickstart sample still calls etldPlusOne(host, PINNED_PSL_SNAPSHOT). I'd drop the second argument:
    const agentEtld1 = etldPlusOne(new URL(agentUrl).hostname);     // step 3, bundled PSL
    const brandEtld1 = etldPlusOne(new URL(brandJsonUrl).hostname);
  2. accounts-and-agents.mdx:380. "as registrable domains under the same snapshot" should be "under the same list".

Also at security.mdx:1428, "that step 3 above requires" sits in step 3 of the quickstart itself, so it reads as a self-reference. A link is clearer: "that step 3 of Discovering an agent's signing keys requires".

What the record overstates

  1. "Fails closed" holds for trust records, but not for the brand_json_url origin binding. Say np.app joins the PRIVATE section. A verifier with the older list gives alice.np.app and bob.np.app the same eTLD+1. If Alice's brand.json lists bob.np.app/mcp, step 3 passes without authorized_operators[], and step 6 takes Bob's keys from his origin. That is the shared-tenancy case step 3 exists to close.

    The decision still stands. Every verifier behaved this way before the listing, and the gap closes when the verifier updates. A pinned snapshot would keep it open until the next release. The DR and the changeset should say this rather than "fails closed" in general. The threat row in agent-identity-3-3.md is fine, because it covers trust records only.

    • DR rationale, after "which also fails closed": "The brand_json_url origin binding goes the other way. An older list gives two tenants of a newly listed platform one eTLD+1, so the binding passes for them without authorized_operators[] until the verifier updates. Every verifier behaved that way before the listing, and a pinned snapshot would keep the gap open until the next release."
    • Changeset: "A verifier whose list lacks a newly added PRIVATE suffix fails closed" becomes "On the trust-record path, a verifier whose list lacks a newly added PRIVATE suffix fails closed."
  2. "Two lists disagree only on suffixes added to, or removed from, the PRIVATE section." I wrote it that way in #8409, and it isn't quite right. The ICANN section changes too, through second-level suffixes under ccTLDs, for example the .bd update in October 2025 and org.ao, edu.ao, and gov.ao in 2024. A new TLD changes nothing, because the PSL default rule already treats an unlisted TLD as a suffix. "mostly in the PRIVATE section" fixes it, and the argument holds for both sections.

  3. "AdCP publishes no PSL snapshot and has no PSL conformance vectors." static/test-vectors/adagents-discovery-redirects.json has four PSL vectors: well-known-multi-label-tld-same-domain-followed and well-known-multi-label-tld-cross-domain-refused (co.uk), and well-known-private-suffix-cross-tenant-refused and well-known-private-suffix-same-tenant-followed (github.io). They're fine, because every list agrees on those suffixes. I'd write "AdCP publishes no PSL snapshot and no snapshot-specific vectors; the PSL vectors in adagents-discovery-redirects.json use suffixes every list agrees on."

DR-0023 note

  1. DR-0026 and DR-0036 each mark the record they amend in part: DR-0023 carries *Amended by DR-0026: …*, and DR-0019 carries *Amended by DR-0036: …*. DR-0023 needs the same for R3a step 1, for example after the design-spec paragraph at line 43: "Amended by DR-0037: R3a step 1 uses a Public Suffix List bundled with the verifier and refreshed through dependency updates; AdCP names no snapshot."

Server integration tests (3/4) failed in "Install dependencies" before any test ran, so a rerun should clear it.

Once these are in, I'll approve.

- Quickstart sample drops PINNED_PSL_SNAPSHOT; the step 3 pointer links
  to Discovering an agent's signing keys instead of reading as a
  self-reference.
- L2: domains compare "under the same list".
- DR-0037: lists differ mostly, not only, in the PRIVATE section; the
  brand_json_url origin binding passes for two tenants of a newly listed
  platform until the verifier updates; the adagents-discovery-redirects
  PSL vectors use suffixes every list agrees on.
- Changeset scopes "fails closed" to the trust-record path.
- DR-0023 carries the Amended by DR-0037 note.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

bokelley commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @KonstantinMirin. All six are in 7f75030, using your wording:

  1. The quickstart sample drops PINNED_PSL_SNAPSHOT, and quickstart step 3 now links to Discovering an agent's signing keys instead of "step 3 above".
  2. L2 now says "under the same list".
  3. The DR rationale adds your brand_json_url origin-binding paragraph, and the changeset scopes "fails closed" to the trust-record path. The threat row is unchanged.
  4. "mostly in the PRIVATE section", plus the ccTLD second-level note and the default-rule point about new TLDs.
  5. "no PSL snapshot and no snapshot-specific vectors", pointing to the adagents-discovery-redirects.json vectors.
  6. DR-0023 now carries the Amended by DR-0037 note after the design-spec paragraph.

The PR body is updated to match. The earlier CI failures were in "Install dependencies", and this push reruns everything.

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Escalate to human review

Escalate — hard approval gate not satisfied.

This PR modifies static/schemas/source/trust/v1/trust.json, which matches the gated glob static/schemas/source/** (gated_paths: true). The current review_decision is REVIEW_REQUIRED, not APPROVED. Per decision rule row 2, a gated path without a recorded human/CODEOWNERS approval MUST escalate — regardless of how clean the diff is.

The reviewer found no Critical/High/Medium findings: the DR-0037 governance amendment (PSL bundled, not pinned) is coherent across the changeset, L1/L2 docs, and DR-0023/DR-0037. The security.mdx cross-reference anchor verifies, schema↔docs coherence holds (trust.json grantee description matches the "bundled list" wording), and the patch changeset is defensible for a normative relaxation with no wire-shape change. There are no fresh blocking findings — but the gate is deterministic and does not lift on diff cleanliness.

This also carries forward the prior escalate posture (same gated-path trigger), which remains correct because the approval requirement is still unmet.

Escalation reasons

  • static/schemas/source/trust/v1/trust.json is under a hard approval gate (static/schemas/source/**) and review_decision is REVIEW_REQUIRED, not APPROVED. Human/CODEOWNERS approval is required before this can merge.

Why human review

  • Modifies static/schemas/source/trust/v1/trust.json (gated path static/schemas/source/**) while review_decision is REVIEW_REQUIRED, not APPROVED — human/CODEOWNERS approval required to satisfy the hard approval gate.
  • This PR touches a path under a hard, non-overridable approval gate (static/schemas/source/trust/v1/trust.json (modified) matches static/schemas/source/**) and the current GitHub review decision is 'REVIEW_REQUIRED', not APPROVED. This is a hard gate enforced in code — Ladon cannot auto-approve until a human/CODEOWNERS approval is recorded, regardless of how clean the diff is.

This branch has not been deployed

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

Labels

ladon/needs-human-review Ladon has escalated this PR for human review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Name the Public Suffix List snapshot verifiers pin, and publish it with each release

2 participants