Skip to content
Open
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
3 changes: 0 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@ jobs:
- name: Agent Skill drift check
run: npm run check:skill

- name: ARD catalog signature check
run: npm run check:ard

- name: Type-check (Astro)
run: npx astro check

Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ pnpm-debug.log*
.wrangler/

# ARD AI Catalog offline signing key — NEVER commit. Back up to secure storage.
# Retired with the ARD trust-manifest signature. Kept ignored so a leftover key
# from before the removal can never be committed by accident.
.ard-signing-key.json

# Claude
Expand Down
59 changes: 29 additions & 30 deletions CLAUDE.md

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions docs/okf-recon.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
Recon for the "publish the spec as an OKF bundle, fix discovery via ARD" plan.
No source files were changed to produce this. Read this before Phase 1.

> **Point-in-time document.** Its ARD signing observations no longer describe the
> site: the `trustManifest` signature, `scripts/sign-ard-catalog.mjs`, and
> `jwks.json` were removed on 23 July 2026 because the verifying key shared an
> origin with the catalogue it signed. See `ops/notes/ard-adoption.md`.

## State reconciliation (what the plan assumed vs. what is already shipped)

| Plan phase | Status today | Notes |
Expand Down
10 changes: 10 additions & 0 deletions ops/notes/ard-adoption.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ reference implementation of [Agentic Resource Discovery](https://agenticresource
(ARD) — the Linux Foundation / Google draft announced June 2026. Not part of the
deployed site; this file is scratch for the write-up.

> **Superseded in part, 23 July 2026.** The signing described in §9 was removed.
> The verifying JWK Set lived at `/.well-known/jwks.json`, on the same origin as
> the catalogue it vouched for — so anyone able to rewrite the catalogue could
> swap the key too, and the signature could not detect the compromise it existed
> for. It also covered `host.trustManifest` only, leaving every `entries` URL
> (the fields an attacker would actually target) unsigned. The catalogue is now
> served as plain JSON. The reasoning is on the
> [spec page](../../src/content/spec/agent-readiness/agentic-resource-discovery.md);
> it makes a better post than the signing walkthrough did.

## The one-sentence pitch

ARD is a discovery layer (not a runtime): a domain publishes an **AI Catalog** at
Expand Down
2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@
"format": "prettier --write .",
"format:check": "prettier --check .",
"assets": "node scripts/generate-assets.mjs",
"sign:ard": "node scripts/sign-ard-catalog.mjs",
"check:ard": "node scripts/sign-ard-catalog.mjs --check",
"sign:skill": "node scripts/check-skill.mjs",
"check:skill": "node scripts/check-skill.mjs --check"
},
Expand Down
8 changes: 1 addition & 7 deletions public/.well-known/ai-catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,7 @@
"displayName": "The Website Specification",
"identifier": "specification.website",
"documentationUrl": "https://specification.website/spec/agent-readiness/agentic-resource-discovery/",
"logoUrl": "https://specification.website/icon-512.png",
"trustManifest": {
"identity": "https://specification.website/.well-known/jwks.json",
"identityType": "https",
"privacyPolicyUrl": "https://specification.website/privacy/",
"signature": "eyJhbGciOiJFUzI1NiIsImtpZCI6Im5mYy14WnJwWVJEam9PalM2UVVaT2E3QVk5cTh1angyU2VaVk43MVBmNkkifQ..H5L3qJ-zcov8WtTxR_tc__y5a0VLkA7y4Agr0Auln5A5JxiqrcHXLEfByFOPR4zqFfWkMLLX19O3TFe6jJLDEA"
}
"logoUrl": "https://specification.website/icon-512.png"
},
"entries": [
{
Expand Down
5 changes: 0 additions & 5 deletions public/.well-known/api-catalog
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,6 @@
"href": "https://specification.website/.well-known/ai-catalog.json",
"type": "application/ai-catalog+json",
"title": "Agentic Resource Discovery (ARD) catalog"
},
{
"href": "https://specification.website/.well-known/jwks.json",
"type": "application/jwk-set+json",
"title": "JWK Set — verifies the ARD catalog signature"
}
],
"license": [
Expand Down
13 changes: 0 additions & 13 deletions public/.well-known/jwks.json

This file was deleted.

6 changes: 0 additions & 6 deletions public/_headers
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,6 @@
Cache-Control: public, max-age=3600
Access-Control-Allow-Origin: *

# JWK Set — public key for verifying the AI Catalog trustManifest signature
/.well-known/jwks.json
Content-Type: application/jwk-set+json; charset=utf-8
Cache-Control: public, max-age=3600
Access-Control-Allow-Origin: *

/.well-known/agent-skills/*/SKILL.md
Content-Type: text/markdown; charset=utf-8
Cache-Control: public, max-age=3600
Expand Down
202 changes: 0 additions & 202 deletions scripts/sign-ard-catalog.mjs

This file was deleted.

8 changes: 8 additions & 0 deletions src/content/changelog/2026-07-23-ard-catalog-unsigned.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: "ARD catalogues: sign only when the key is anchored elsewhere"
date: "2026-07-23"
type: changed
relatedSlugs: [agentic-resource-discovery]
---

[Agentic Resource Discovery](/spec/agent-readiness/agentic-resource-discovery/) now explains when a `trustManifest` signature is worth anything and when it is theatre: a detached JWS whose verifying key sits on the same origin as the catalogue folds to the very compromise it is meant to detect, and it leaves `entries` — the endpoint URLs an attacker would actually rewrite — unsigned. This site removed its own ES256 signature accordingly and serves the catalogue as plain JSON.
17 changes: 13 additions & 4 deletions src/content/spec/agent-readiness/agentic-resource-discovery.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ status: optional
order: 86
appliesTo: [all]
relatedSlugs: [dns-aid, mcp-and-tool-discovery, a2a-agent-cards, agent-skills-discovery, okf-bundle, link-headers, well-known-overview]
updated: "2026-06-19T12:00:00.000Z"
updated: "2026-07-23T00:00:00.000Z"
sources:
- title: "Agentic Resource Discovery (ARD) specification"
url: "https://agenticresourcediscovery.org/"
Expand Down Expand Up @@ -43,24 +43,33 @@ Beyond the well-known file itself, three mechanisms point agents at it: a `<link
## Why it matters

- **One fetch, typed answer.** Instead of guessing at well-known paths one by one, an agent reads a single manifest that names every capability and its media type.
- **Domain-anchored trust.** An optional `trustManifest` (SPIFFE ID, DID, or HTTPS identity, plus attestations and an optional JWS signature) lets a client verify the publisher cryptographically. The signature is a detached JWS over the JCS-canonicalised ([RFC 8785](https://www.rfc-editor.org/rfc/rfc8785)) trust manifest with the `signature` field removed; for an HTTPS identity, the verifier fetches the JWK Set at the identity URL and selects the key by the JWS `kid`.
- **Domain-anchored trust, if you can anchor it.** An optional `trustManifest` (SPIFFE ID, DID, or HTTPS identity, plus attestations and an optional JWS signature) lets a client verify the publisher cryptographically. The signature is a detached JWS over the JCS-canonicalised ([RFC 8785](https://www.rfc-editor.org/rfc/rfc8785)) trust manifest with the `signature` field removed; for an HTTPS identity, the verifier fetches the JWK Set at the identity URL and selects the key by the JWS `kid`. Read the next section before reaching for it — an HTTPS identity on your own origin is weaker than it looks.
- **Registry-friendly.** Registries crawl published catalogs and make them searchable, so a capability listed once becomes discoverable across the agentic web.

## How to implement

Publish `/.well-known/ai-catalog.json` with a `specVersion`, a `host` block, and one entry per capability you actually run. Reference your existing MCP and A2A cards by `url` rather than duplicating them. Advertise the manifest with a `link rel`, the `robots.txt` `Agentmap:` directive, and, if you publish DNS, the `_catalog._agents` record. Don't list endpoints you don't offer.

This site ships it: [`/.well-known/ai-catalog.json`](/.well-known/ai-catalog.json) catalogues our MCP server, A2A agent, [Agent Skill](/spec/agent-readiness/agent-skills-discovery/), and [OKF bundle](/spec/agent-readiness/okf-bundle/), advertised through the `Link` header, a `link rel`, `robots.txt`, and a `_catalog._agents` DNS record. Each entry carries both `mediaType` and `type` plus `representativeQueries`. The host `trustManifest` carries a detached ES256 JWS signature covering the manifest only (not the entries), so entries can change without re-signing; the public key is published as a JWK Set at [`/.well-known/jwks.json`](/.well-known/jwks.json). We sign offline and commit the signature — the private key never enters CI.
### Sign it only if the key lives somewhere the catalogue doesn't

The obvious way to use `trustManifest` is an HTTPS identity pointing at a JWK Set on your own origin. Think about what that buys. An attacker who can rewrite `/.well-known/ai-catalog.json` can rewrite `/.well-known/jwks.json` in the same breath, substitute their own key, and produce a manifest that verifies perfectly. The signature is defeated by exactly the compromise it exists to detect, because the document and the key that vouches for it share a fate.

A signature is only worth its weight when the verifying key is resolvable **independently of the signed document** — a DNSSEC-signed record, a key pinned by the verifier out of band, or a JWK Set on infrastructure an origin compromise would not reach. Absent one of those, an unsigned catalogue served over HTTPS makes the same guarantee with none of the machinery: TLS already proves the bytes came from the domain.

Note also that the signature covers the trust manifest **only, not `entries`**. That is convenient — entries change without re-signing — but it means the fields worth attacking are the unsigned ones. Rewriting an entry's `url` to point an agent at a hostile MCP endpoint leaves a valid signature intact.

**This site publishes the catalogue unsigned, deliberately.** [`/.well-known/ai-catalog.json`](/.well-known/ai-catalog.json) catalogues our MCP server, A2A agent, [Agent Skill](/spec/agent-readiness/agent-skills-discovery/), and [OKF bundle](/spec/agent-readiness/okf-bundle/), advertised through the `Link` header, a `link rel`, `robots.txt`, and a `_catalog._agents` DNS record; each entry carries both `mediaType` and `type` plus `representativeQueries`. We previously shipped a detached ES256 JWS with the JWK Set on this origin, and removed it: it protected three static fields, left every entry unsigned, and folded to any compromise that could reach the catalogue. Publishing it and explaining why is more use to you than a signature that verifies and proves nothing.

## Common mistakes

- Inventing fields. The entry schema is small — `identifier`, `displayName`, the media-type field (`mediaType` and/or `type`), and exactly one of `url` or `data`.
- A URN publisher segment that doesn't match the `trustManifest` identity domain — verification then fails.
- Signing the manifest with a key served from the same origin as the manifest, and treating the result as tamper-evidence. It is not: whoever can change one can change the other.
- Listing aspirational capabilities. The catalogue is a contract; only list what resolves.

## Verification

- `curl -s https://example.com/.well-known/ai-catalog.json | jq .` returns valid JSON with `specVersion` and `entries`.
- Each entry `url` resolves to a document of the declared `mediaType`.
- The `Agentmap:` line is present in `robots.txt` and the `ai-catalog` `Link` rel is on the homepage response.
- If a `trustManifest.signature` is present, it verifies: fetch the JWK Set at the `identity` URL, JCS-canonicalise the manifest minus `signature`, and check the detached JWS against the key named by its `kid`.
- If a `trustManifest.signature` is present, it verifies: fetch the JWK Set at the `identity` URL, JCS-canonicalise the manifest minus `signature`, and check the detached JWS against the key named by its `kid`. Then check the harder question — whether the `identity` URL is reachable by anyone who could alter the catalogue itself. If it is, the verification is circular.
Loading