Protocol Discovery · Emerging · Medium impact
Cryptographically signed agent card
A2A v1.0 makes identity verifiable through JWS (RFC 7515).
- Emerging
- Medium impact
- Draft standard
| Property | Value |
|---|---|
| Check id | a2a-signed |
| Dimension | Protocol Discovery (25%) |
| Tier | Emerging — pool 5 pts |
| Weight inside tier | 1 |
| Impact | Medium |
| Maturity | Draft — A published specification that is not yet ratified. |
| Conditional | No |
| Publishing skill | a2a-card-signing |
| Standards | RFC 7515 |
What the scanner requests
-
/.well-known/agent-card.json -
/.well-known/agent.json
What is measured
The scanner fetches the A2A card and inspects its signatures array. It passes only when a signature is cryptographically verified: at least one entry is a JWS (RFC 7515) object with base64url protected and signature members whose protected header names alg, typ and kid, and the public key published at the jku URL verifies the signature over the card canonicalized per RFC 8785 with the signatures member removed, as A2A v1.0 requires. It partially passes when a signature is structurally complete but was not verified — the referenced JWKS is on another host or outside the probe set so its response was not observed, the JWKS does not publish the key, or the algorithm carries no digest this scanner can check — and it fails when a key from a published JWKS is available and the signature does not verify against it.
Reproduce it yourself
You do not have to take our word for any of this. Run the same measurement against your own domain:
DOMAIN=https://your-domain.example
curl -sS "$DOMAIN/.well-known/agent-card.json" | jq -r '.signatures[0].protected' | python3 -c "import sys,base64,json;p=sys.stdin.read().strip();p+='='*(-len(p)%4);print(json.dumps(json.loads(base64.urlsafe_b64decode(p)),indent=2))"
The fix
This is the artifact to ship. It is the complete change, not a description of one.
// npm i jose canonicalize -> node sign-agent-card.mjs
import { readFileSync, writeFileSync } from 'node:fs';
import canonicalize from 'canonicalize';
import { CompactSign, exportJWK, generateKeyPair } from 'jose';
const card = JSON.parse(readFileSync('agent-card.json', 'utf8'));
const { privateKey, publicKey } = await generateKeyPair('ES256');
const { signatures, ...payload } = card; // A2A v1.0 excludes signatures
const canonical = canonicalize(payload); // RFC 8785 canonical JSON
const jws = await new CompactSign(new TextEncoder().encode(canonical))
.setProtectedHeader({
alg: 'ES256', typ: 'JOSE', kid: 'key-1',
jku: 'https://your-domain.example/.well-known/jwks.json'
})
.sign(privateKey);
const [protectedHeader, signature] = jws.split('.');
const signed = { ...card, signatures: [{ protected: protectedHeader, signature }] };
writeFileSync('agent-card.signed.json', JSON.stringify(signed, null, 2));
const jwk = { ...(await exportJWK(publicKey)), kid: 'key-1', use: 'sig', alg: 'ES256' };
writeFileSync('jwks.json', JSON.stringify({ keys: [jwk] }, null, 2));
Standards referenced
-
RFC 7515
Related checks
| Check | Tier | Impact | Same skill |
|---|---|---|---|
mcp-card — MCP server card | Essential | High | — |
openapi — OpenAPI definition | Essential | High | — |
oauth-discovery — OAuth discovery documents | Essential | High | — |
api-catalog — API catalog | Recommended | Medium | — |
agent-skills — Agent Skills declaration | Recommended | Medium | — |
a2a-card — A2A agent card | Recommended | Medium | — |
auth-md — /auth.md agent authentication guide | Emerging | Medium | — |
webmcp — WebMCP browser tool declaration | Emerging | Medium | — |