Repository navigation
Align agent resolution and webhook pin checks with AdCP 3.3 (adcp#7882) #1283
Copy link
Copy link
Closed
Labels
Description
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
- Match the agent by canonical URL, never by type or id alone.
- Canonicalize the agent URL and every candidate
agents[].urlperdocs/reference/url-canonicalization. Exactly one entry must match. - This applies to webhook verification and governance
iss, which used to compare byte-for-byte. The capabilitiesverifier_constraints.agent_url_matchis nowcanonical. - A
typeoridfilter may narrow the match. It must never be the selector.
- Canonicalize the agent URL and every candidate
- House Portfolio operator records.
- Candidates are
house.agents[]plus everybrands[].agents[]. - Matches that agree on
typeand on the resolved JWKS source count as one entry. Otherwise more than one match isrequest_signature_brand_json_ambiguous.
- Candidates are
- 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 theidentity.key_originscheck. - 3.x fallback: if the field is absent, use
/.well-known/brand.jsonat 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 specificrequest_signature_*cause.
- Fetch the seller's
- Publisher
signing_keyspin: 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
kidalone is not enough. A pinned entry without complete public-key parameters matches nothing, andrevoked_atstill 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_originsis no longer skipped for pinned keys.resolveAgenthas no publisher context, so this lives in the webhook verifier. The API probably needs a way to pass the applicable pins in.
- Origin-binding fallback. Read
authorized_operators[]only from a House Portfolio. Any entry whosedomainequals the agent's eTLD+1 passes. Itsbrands/countriesdon't scope key resolution. - Governance JWS.
- Match
isscanonically against exactly onetype: "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.jsonfrom its host.
- Match
- 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.
- A cached or onboarding mapping from agent URL to brand.json must have come from, or been re-confirmed against, the agent's
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_originsmismatch 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
Reactions are currently unavailable
Activity
bokelley commentedon Oct 2, 2026
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:
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_agentinbrand_jwks.pyneeds canonical-URL matching,webhook_verifier.pyneeds publisher-pin context threaded in, andkey_origins.pyneeds the pin carve-out removed for webhook keys — but the spec text lives on the unmergedadcp#7882PR. 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, andadagents.py). The implementation brief below is for reference when that happens:src/adcp/signing/brand_jwks.py(_pick_agent→ canonical-URL match;_select_agent→ scanhouse.agents[]+ allbrands[].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 fromidentity.brand_json_url),src/adcp/adagents.py(force-refresh before final rejection; pass applicable pins to webhook verifier)kid,key_originsmismatch on pinned webhook key, twoagents[]entries differing only by host case (ambiguous)Triaged by Claude Code. Session: https://claude.ai/code/session_01658x6Kug5Wr2zH6knuKG5g
Generated by Claude Code
bokelley commentedon Oct 13, 2026
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.