Skip to content

Align agent resolution and webhook pin checks with AdCP 3.3 (adcp#7882) #1283

Closed

Description

@bokelley

Likely touch points: src/adcp/signing/brand_jwks.py (BrandJsonJwksResolver, which selects by agent type), src/adcp/signing/key_origins.py, src/adcp/adagents.py (pin handling), and resolve_agent / verify_from_agent_url.

adcontextprotocol/adcp#7882 adds one Agent resolution algorithm to docs/building/by-layer/L1/security.mdx (anchor #agent-resolution). It changes how verifiers match an agent and pick its keys. The change is a minor protocol change for AdCP 3.3 (main is in 3.3 beta). Spec text is on that PR until it merges.

What verifiers need to change

  1. Match the agent by canonical URL, never by type or id alone.
    • Canonicalize the agent URL and every candidate agents[].url per docs/reference/url-canonicalization. Exactly one entry must match.
    • This applies to webhook verification and governance iss, which used to compare byte-for-byte. The capabilities verifier_constraints.agent_url_match is now canonical.
    • A type or id filter may narrow the match. It must never be the selector.
  2. House Portfolio operator records.
    • Candidates are house.agents[] plus every brands[].agents[].
    • Matches that agree on type and on the resolved JWKS source count as one entry. Otherwise more than one match is request_signature_brand_json_ambiguous.
  3. Webhook key discovery starts from identity.brand_json_url.
    • Fetch the seller's get_adcp_capabilities, then run discovery steps 1–7: origin binding, canonical match, and the identity.key_origins check.
    • 3.x fallback: if the field is absent, use /.well-known/brand.json at the agent's host, or at its eTLD+1 when the host serves none. Skip the steps that need the field (2, 3 and 7).
    • Webhook resolution failures report webhook_signature_key_unknown. Log the specific request_signature_* cause.
  4. Publisher signing_keys pin: narrowing intersection.
    • This replaces the old rule, where the pin was authoritative and the JWKS advisory.
    • For a sell-side signature about publisher P's inventory, accept a key only if the agent's JWKS publishes it and its RFC 7638 thumbprint matches an entry in P's pin.
    • Matching by kid alone is not enough. A pinned entry without complete public-key parameters matches nothing, and revoked_at still applies.
    • Take the applicable publishers from the verifier's own record of the media buy, never from the payload. When several publishers apply, the key must match every pin.
    • Before final rejection, force-refresh adagents.json.
    • key_origins is no longer skipped for pinned keys.
    • resolveAgent has no publisher context, so this lives in the webhook verifier. The API probably needs a way to pass the applicable pins in.
  5. Origin-binding fallback. Read authorized_operators[] only from a House Portfolio. Any entry whose domain equals the agent's eTLD+1 passes. Its brands/countries don't scope key resolution.
  6. Governance JWS.
    • Match iss canonically against exactly one type: "governance" entry.
    • Pick the collection the way the brand-authorization cross-check's step 2 does, using the governed request's brand domain. Never a sibling brand's.
    • For signed requests, the buyer's brand.json is the exact operator record that agent resolution selected. Don't refetch /.well-known/brand.json from its host.
  7. Cached mappings.
    • A cached or onboarding mapping from agent URL to brand.json must have come from, or been re-confirmed against, the agent's brand_json_url.
    • Re-confirm at least as often as the brand.json cache lifetime.

Signatures that newly fail (worth tests)

  • A pinned key that the agent's JWKS doesn't publish.
  • A pin entry with only a kid.
  • A key_origins mismatch on a pinned webhook key.
  • Two agents[] entries that differ only by host case or default port, which are now ambiguous.

The brand-response cross-check and designated-task authorship check are unchanged.

🤖 Generated with Claude Code

Activity

  1. added
    claude-triagingTriage routine is actively working on this issue (1-3 min)
    on Oct 2, 2026
  2. bokelley commented on Oct 2, 2026

    @bokelley
    ContributorAuthor

    Triage

    Classification: Feature request — AdCP 3.3 protocol alignment
    Bucket(s): signing (no matching repo label exists for this bucket — gap noted in run summary)
    Status: deferred
    Milestone: omitted (no v3.3 milestone or label found in repo)

    What the experts said:

    • ad-tech-protocol-expert (synthesized from spec + code review): Seven distinct algorithm changes across agent resolution, webhook key discovery, and publisher-pin semantics. Core shift is canonical-URL as the primary agent selector (replacing type/id) and a narrowing intersection rule for pins (JWKS must publish + RFC 7638 thumbprint must match pin — previous rule was pin-authoritative). Both are correct per the AdCP trust model, but depend on the final spec wording to avoid drift.
    • security-reviewer (synthesized): Items 1, 4, 5, 6 change what keys are accepted for signing; item 4 in particular reverses the existing carve-out in key_origins.py ("skip this check for publisher-pinned JWKS sources"), which is a security boundary change that requires careful review. Implementing against an open spec PR risks shipping a partial or inconsistent model if the spec is amended before merge.

    My take: The implementation path is clear — _pick_agent in brand_jwks.py needs canonical-URL matching, webhook_verifier.py needs publisher-pin context threaded in, and key_origins.py needs the pin carve-out removed for webhook keys — but the spec text lives on the unmerged adcp#7882 PR. The right move is to hold until the spec is final.

    Blocked-on: adcontextprotocol/adcp#7882 — resurfaces on merge.

    Once adcp#7882 merges, this should be flagged for human review (security-sensitive, multiple breaking verification-semantic changes touching brand_jwks.py, key_origins.py, webhook_verifier.py, and adagents.py). The implementation brief below is for reference when that happens:

    • Scope: src/adcp/signing/brand_jwks.py (_pick_agent → canonical-URL match; _select_agent → scan house.agents[] + all brands[].agents[]), src/adcp/signing/key_origins.py (remove publisher-pin carve-out for webhook path), src/adcp/signing/webhook_verifier.py (thread publisher pins into verifier; start key discovery from identity.brand_json_url), src/adcp/adagents.py (force-refresh before final rejection; pass applicable pins to webhook verifier)
    • New tests needed: pinned key absent from JWKS, pin entry with only kid, key_origins mismatch on pinned webhook key, two agents[] entries differing only by host case (ambiguous)
    • Breaking by default: pin semantics change (previously pin-authoritative; new rule requires JWKS intersection) — adoption path or feature-flag needed

    Triaged by Claude Code. Session: https://claude.ai/code/session_01658x6Kug5Wr2zH6knuKG5g


    Generated by Claude Code

  3. added
    enhancementNew feature or request
    and removed
    claude-triagingTriage routine is actively working on this issue (1-3 min)
    on Oct 2, 2026
  4. bokelley commented on Oct 13, 2026

    @bokelley
    ContributorAuthor

    Follow-on for the rest of the 3.3 agent-identity work (Web Bot Auth profile, trust.json, grants, onboarding posture, relationship status) is tracked in #1500. It also covers #1214.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions