Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions .changeset/root-of-trust-key-discovery-gap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
---

spec(security): name the key-discovery gap and specify the 3.x root-of-trust baseline

AdCP 3.0's identity, governance, and pointer-file layers all discover
the verifying public key from the counterparty's own infrastructure —
RFC 9421 buyer keys from the buyer agent's JWKS, governance JWS keys
from the governance agent's JWKS, agent signing keys from
`brand.json`, and pointer files from `/.well-known/adagents.json`.
Every one of those discovery paths trusts the counterparty origin as
the root of trust. TLS does not close this — the certificate is issued
to the hostname the attacker has compromised — so an attacker who
controls a counterparty's CDN, DNS, or `/.well-known` path can serve
attacker-controlled keys and signatures verify clean against those
keys.

The prior security model listed HMAC, RFC 9421, JWKS, and `brand.json`
attestation as individual mechanisms without ever naming the shared
assumption underneath them. That omission is the reason the R-3
pointer-swap threat, the agent-signing-key swap threat, and the
governance-JWKS-swap threat all feel like separate problems — they are
the same problem one layer deeper. Naming it is the 3.x precondition
for the 4.0 fix.

Adds two pieces:

- `security-model.mdx` — a new **Trust anchors and the key-discovery
gap** section between *What AdCP does not do in 3.0* and *What is
outside the protocol*. Enumerates where 3.0 relies on the
counterparty origin (RFC 9421 JWKS, governance JWKS, `brand.json`
agent keys, `authoritative_location` pointers), specifies the
TOFU-with-continuity baseline 3.x actually delivers, and documents
four multi-source controls that raise the bar without a registry:
DNS-TXT cross-check, publication-delay / continuity windows on
never-before-seen keys, out-of-band key-rotation signalling, and
rotation-validity discipline. Then sketches the 4.0 centralized
publisher-key registry — enrollment, append-only rotation log,
public queryability, governance-neutral operation, JWKS wire
compatibility — with the explicit caveat that it is not a 3.x
requirement but is the anchor the 3.x controls are designed to feed
into.

- `known-limitations.mdx` §Authentication and identity — adds a
**No centralized publisher-key registry** bullet and a cross-link
to the new section, so the limitation is surfaced on the canonical
non-goals page rather than only in the architectural discussion.

No schema change, no new wire format. The 3.x deliverable is honesty
about the trust model plus a normative baseline operators can actually
implement; the 4.0 deliverable is the registry itself, which is logged
as a successor track rather than hand-waved into 3.x.
38 changes: 38 additions & 0 deletions docs/building/understanding/security-model.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,44 @@ Knowing what a protocol doesn't do is part of evaluating it. The canonical, main

None of these are hidden. Each is a visible edge of the specification and a candidate for future work.

## Trust anchors and the key-discovery gap

The identity, governance, and pointer-file layers above all rest on the same hidden assumption: that the public keys verifying signatures can be discovered honestly. In 3.0, that discovery path is counterparty-rooted in every case:

- **RFC 9421 buyer keys** — JWKS fetched from the buyer agent's own domain or `.well-known` path.
- **Governance JWS keys** — JWKS fetched from the governance agent's own domain.
- **Agent signing keys** — publisher-attested in `brand.json` `agents[].signing_keys[]`, fetched from the publisher's own `/.well-known`.
- **`adagents.json` authoritative pointers** — fetched from the publisher's own `/.well-known`, with the pointer-swap threat documented in [managed-networks security](/docs/governance/property/managed-networks#security-considerations).

Every one of those steps trusts the counterparty's own infrastructure as the root of trust. TLS does not close this — the certificate is issued to the hostname the attacker has compromised, so it verifies clean. An attacker who controls a counterparty's CDN, DNS, or `/.well-known` path can therefore serve attacker-controlled keys, and every signature made with those keys will verify against them.

What 3.0 actually delivers is **trust-on-first-use with continuity**: verifiers cache the first-seen keys, pin rotations against the prior key set, and alert on unexpected changes. This raises the bar — an attacker must either control the counterparty origin for long enough to look routine, or swap keys at onboarding before the victim has cached anything — but it does not close the gap. It is an honest description of the 3.x posture, not a claimed cryptographic root of trust.

### What raises the bar in 3.x

Implementers SHOULD layer independent attestation sources rather than rely on any single origin. Each control below converts a silent key-swap into a detectable event within a bounded window:

- **Multi-source cross-check.** When a signing key appears in `brand.json`, verify it matches the key used on signed agent responses *and* a DNS-based attestation (a TXT record at the publisher's apex binding the key fingerprint to the domain, rotated in lock-step with the key material). Compromise of the HTTPS origin alone does not also forge DNS; an attacker must break both surfaces simultaneously.
- **Publication-delay / continuity windows.** Treat a never-before-seen key as provisional for a declared period (24–72 h) during which high-value operations continue to be verified against the previously cached key, and alerts fire on the rotation. A legitimate rotation survives this with operator acknowledgement; an attacker-injected key surfaces before any spend moves.
- **Out-of-band key-change signalling.** Publishers, governance agents, and buyer agents SHOULD announce key rotations through channels the counterparty origin cannot forge — vendor status pages, ads.txt cross-references, partner announcement lists, direct operator notification. The protocol does not prescribe the channel; the requirement is that a channel exists and the verifier watches it.
- **Rotation-validity discipline.** Keys past their declared rotation window are an attack surface, not a preference signal. Verifiers SHOULD reject signatures made with a key past its declared validity rather than silently falling back to older cached material, and SHOULD refuse to accept a rotation that sets `not_after` in the past as a legitimate rollover.

These controls do not substitute for a root of trust. They make a key-swap attack detectable and costly rather than silent and cheap — which is the security posture 3.x can honestly deliver.

### What AdCP 4.0 needs: a centralized publisher-key registry

The permanent fix is a centralized registry analogous in spirit to Certificate Transparency for TLS or `sellers.json` for the ad-tech identity layer. The minimal protocol-relevant properties:

1. **Publisher enrollment.** Each publisher, governance agent, and sales-agent domain registers a root verification key under its domain identity. The registry binds `{domain, root_key_fingerprint, enrolled_at}` and attests domain control through a documented challenge (DNS, HTTPS, or equivalent).
2. **Append-only rotation log.** Rotations are appended, not overwritten. The registry publishes a transparency log so a key rotation cannot be backdated, withdrawn, or selectively served to different verifiers.
3. **Public queryability.** Buyers, sellers, and validators query the registry by domain and receive the current root-key set plus the rotation history. The registry is a discovery index, not a signing authority — it never holds private keys and cannot issue signatures on any party's behalf.
4. **Governance-neutral operation.** The registry is operated by an industry body with published governance, documented key-ceremony transparency for the registry's own signing keys, and a succession plan independent of any single vendor.
5. **Backwards-compatible wire format.** Keys in the registry surface through the same JWKS format that verifiers already consume. A 3.x verifier's switch to registry-anchored trust is a configuration change (point JWKS discovery at the registry-index URL), not a new protocol surface.

This is **explicitly not a 3.x requirement.** It is logged as a 4.0 track so implementers who build the in-protocol attestation surfaces today — `brand.json` `agents[].signing_keys[]`, `authoritative_location`, signed governance JWS — can shape their data so a later registry lookup can anchor it without protocol breakage. Specifically, implementers SHOULD keep key declarations at stable single-purpose URIs, SHOULD carry key fingerprints alongside full key material (the registry can only anchor what it can unambiguously identify), and SHOULD NOT conflate signing keys with transport keys.

Until the registry exists, the multi-source controls above are the 3.x normative baseline. They are the difference between "an attacker who compromises one counterparty origin gets silent authority" and "the compromise produces a detectable signal within a bounded window." 3.x promises the second; it does not promise the first.

## What is outside the protocol

AdCP specifies the wire. It does not specify — and cannot substitute for — any of the following:
Expand Down
Loading