Skip to content

docs(trusted-match): bind seller_agent_url into the TMP request signature#5660

Merged
ohalushchak-exadel merged 1 commit into
mainfrom
ohalushchak-exadel/tmp-sign-seller-agent-url
Jun 22, 2026
Merged

docs(trusted-match): bind seller_agent_url into the TMP request signature#5660
ohalushchak-exadel merged 1 commit into
mainfrom
ohalushchak-exadel/tmp-sign-seller-agent-url

Conversation

@ohalushchak-exadel

Copy link
Copy Markdown
Collaborator

Summary

Adds seller_agent_url to the Context Match and Identity Match signed-fields tables in the TMP specification.

seller_agent_url is the asking seller's agent URL on the request. It selects which seller's registered active package set the receiver evaluates (Seller Agent Attribution) and is the lookup key receivers use to resolve the publisher's signing keys for verification. Leaving it out of the signed bytes lets a holder of a valid registry key replay a captured signature under a different seller's URL to read another seller's offers or active package set — a structural break of the "binds the request to a specific seller" property the rest of the section already assumes.

Both reference implementations of TMP signing already cover seller_agent_url in the signed input — this change brings the normative spec into alignment.

Changes

  • Context Match signed fields: insert seller_agent_url immediately after type in the newline-joined input, compared using the AdCP URL canonicalization rules.
  • Identity Match signed fields: insert seller_agent_url immediately after request_id in the JCS canonical object.
  • Context Match signature reuse: extend the cache-reuse key for static placement signatures from (placement_id, provider_endpoint_url) to (seller_agent_url, placement_id, provider_endpoint_url) so that a cached signature under one seller cannot be reused under another.
  • Signing model prose: add a paragraph next to the existing provider_endpoint_url binding explanation describing the same role for seller_agent_url.

Compatibility note

This changes the signed bytes for both request types. Verifiers that have been following the spec literally (without seller_agent_url) will reject signatures from updated signers and vice versa during rollout. Both signers and verifiers should land the change together.

Test plan

  • Repo's docs/lint/typecheck suites (run via the local pre-commit and pre-push hooks)
  • CI green on the PR
  • Spec preview renders the updated tables correctly
  • Cross-check that the in-tree reference implementation's signing input matches the updated tables field-for-field

🤖 Generated with Claude Code

…ture

seller_agent_url selects which seller's registered active package set the
receiver evaluates and is the lookup key receivers use to resolve the
publisher's signing keys; leaving it out of the signed bytes lets a holder
of a valid registry key replay a captured signature under a different
seller's URL to read another seller's offers or active package set.

Adds seller_agent_url to the Context Match and Identity Match
signed-fields tables and extends the Context Match cached-signature
reuse key to (seller_agent_url, placement_id, provider_endpoint_url).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@aao-release-bot aao-release-bot 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.

Clean fix. Closes a real replay gap by binding the signature to the seller it already claims to be scoped to — fail-closed beats fail-open.

The structural argument holds: seller_agent_url is the lookup key receivers use to resolve a seller's active package set, and it was already required on both request bodies but absent from the signed bytes. That gap let a holder of a valid registry key replay a captured signature under a different seller_agent_url to read another seller's active set. Putting it on the wire-signed surface is the right shape.

Things I checked

  • Field exists where the spec says it does. seller_agent_url is required in both wire schemas — static/schemas/source/tmp/context-match-request.json:228 and static/schemas/source/tmp/identity-match-request.json:196. The "From the request body" source claim is accurate. No schema is touched, so no spec drift.
  • Anchor and reference resolve. #seller-agent-attribution exists (specification.mdx:404); /docs/reference/url-canonicalization resolves to docs/reference/url-canonicalization.mdx. The canonicalization-not-byte-equality framing matches the field's own schema description.
  • Context Match order is load-bearing; the insertion is deliberate. Context Match is newline-joined in table order, so inserting seller_agent_url at position 2 (after type, before property_rid) defines the canonical signing order. Identity Match is RFC 8785 JCS, which sorts keys lexicographically — so its table position is cosmetic, not wire-affecting. Both placements are safe.
  • Cache-key extension is correct. Widening the reuse key from (placement_id, provider_endpoint_url) to (seller_agent_url, placement_id, provider_endpoint_url) is exactly what keeps a cached signature from crossing sellers. Without it the new binding would be defeated by the cache.
  • Changeset gate. docs/trusted-match/ is not in PROTOCOL_SCOPED_PATHS (scripts/check-changeset-protocol-scope.cjs), so CI does not require a changeset here. No block.

Follow-ups (non-blocking — file as issues)

  • Breaking interop, no version marker. The PR's own compatibility note is right: this changes the signed bytes for both request types, and updated signers/verifiers reject un-updated ones during rollout. The repo's changeset gate keys off schema source, and this PR is docs-only, so it slips the gate cleanly — every prior seller_agent_url change (#5410/#5411 at 895f74a19) carried a changeset only because it also edited context-match-request.json. Worth a coordinated-rollout note in the changelog even though the gate doesn't compel one, so adopters don't desync silently.
  • Test-plan cross-check has no in-tree target. The unchecked item — "Cross-check that the in-tree reference implementation's signing input matches the updated tables field-for-field" — has nothing to verify against in this repo; there is no in-tree TMP signing implementation. The field-for-field check that actually matters is the Context Match position-2 insertion against the external reference impls you cite. That can't be confirmed from this diff. Confirm it lands in lockstep with the signer/verifier code.

Minor nits (non-blocking)

  1. Identity Match "immediately after request_id" reads as ordering when it isn't. Since the Identity Match input is JCS, key order in the table is documentation-only — an implementer who treats the table row position as significant for the hash would be misled. The section already explains JCS upstream; a half-sentence pinning that the table order is presentational for this request type would remove the ambiguity. specification.mdx:540.

LGTM. Follow-ups noted below.

@ohalushchak-exadel ohalushchak-exadel merged commit 9d90533 into main Jun 22, 2026
19 checks passed
@ohalushchak-exadel ohalushchak-exadel deleted the ohalushchak-exadel/tmp-sign-seller-agent-url branch June 22, 2026 18:03
@mintlify

mintlify Bot commented Jun 22, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
adcp 🔴 Failed Jun 22, 2026, 6:26 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant