Signed Agent Cards: preventing poisoning and spoofing with JWS
Os artigos são publicados em inglês; traduções são exibidas quando disponíveis.
When AI agents discover each other across public networks by fetching an agent.json file,
trust cannot depend on trusting the network. An unsigned card is a single DNS spoof or CDN misconfiguration away
from redirecting autonomous systems to malicious endpoints. In A2A v1.0, Signed Agent Cards solve this with JSON Web Signatures.
Why unsigned cards are vulnerable
In the A2A protocol specification, discovery is designed to be lightweight, static, and unauthenticated: a client agent performs a simple HTTP GET request to /.well-known/agent.json.
While this simplicity enabled rapid ecosystem adoption, it introduces a severe attack surface known in enterprise security as Agent Card Poisoning:
- Endpoint Redirection: An attacker who compromises a DNS entry or an edge proxy can rewrite the
supportedInterfaces[].urlfield, silently sending sensitive prompt payloads to an eavesdropping or impersonation server. - Capability & Prompt Injection: By tampering with the
skillsarray ordescriptionfields, malicious actors can deceive calling agents into delegating high-privilege financial or administrative tasks to an unvetted model. - Identity Spoofing: Without cryptographic verification, anyone can host an agent card claiming that
provider.organizationis an established financial institution or software vendor.
To defend multi-agent architectures against these risks, the official specification maintained by the a2aproject/A2A repository formalizes the signatures array based on RFC 7515: JSON Web Signature (JWS).
Anatomy of the signatures array
In an A2A v1.0 Agent Card, the top-level signatures property holds an array of signature objects. This allows a card to be signed by multiple authorities—for example, the software vendor who authored the agent, and an enterprise compliance auditor who certified its security boundaries.
Each entry follows either standard JWS Flattened JSON serialization or the canonical JWS compact format:
{
"name": "Treasury Reconciliation Agent",
"version": "1.2.0",
"supportedInterfaces": [
{
"url": "https://agents.acme-finance.com/a2a/v1",
"protocolBinding": "JSONRPC",
"protocolVersion": "1.0"
}
],
"signatures": [
{
"protected": "eyJhbGciOiJFUzI1NiIsImtpZCI6IjIwMjYtMDktY29yZSIsInR5cCI6IkpXUyJ9",
"signature": "MEQCIFz8fV9Q2b6HqVqYx3...7k1vU8X3g"
}
]
}
The fields within each signature object operate as follows:
protected: A Base64URL-encoded JSON string containing the JWS header parameters:alg: The cryptographic algorithm used (such asES256orRS256).kid: Key identifier, resolving to the public verification key hosted at the provider's trusted key store.crit: (Optional) Array of critical header extensions that the verifying agent must recognize.
signature: The Base64URL-encoded digital signature computed over the normalized payload and protected header.
Signing an agent.json with ES256
To sign an Agent Card, the document payload must be deterministically canonicalized (stripping any existing signatures field prior to signing) to guarantee consistent digest generation across different programming languages and runtimes.
Here is a reference implementation in Node.js using the standard Web Crypto API:
import crypto from 'node:crypto';
import fs from 'node:fs';
// 1. Read card and remove any existing signatures for canonical payload
const rawCard = JSON.parse(fs.readFileSync('agent.json', 'utf8'));
const { signatures, ...unsignedPayload } = rawCard;
const payloadString = JSON.stringify(unsignedPayload);
// 2. Prepare JWS Protected Header
const header = {
alg: 'ES256',
kid: 'https://acme-finance.com/.well-known/jwks.json#key-2026',
typ: 'JWS'
};
const base64Url = (str) => Buffer.from(str).toString('base64url');
const protectedHeaderB64 = base64Url(JSON.stringify(header));
const payloadB64 = base64Url(payloadString);
const signingInput = `${protectedHeaderB64}.${payloadB64}`;
// 3. Sign using your private EC key (P-256)
const privateKeyPem = fs.readFileSync('private-key.pem', 'utf8');
const signer = crypto.createSign('SHA256');
signer.update(signingInput);
signer.end();
const signatureB64 = signer.sign(privateKeyPem, 'base64url');
// 4. Attach signature block back to the agent card
const signedCard = {
...rawCard,
signatures: [
{
protected: protectedHeaderB64,
signature: signatureB64
}
]
};
fs.writeFileSync('agent.signed.json', JSON.stringify(signedCard, null, 2));
console.log('✓ Agent Card successfully signed with ES256');
How client agents verify signatures
When a client agent retrieves an Agent Card from an unvetted peer, verification must occur before inspecting capabilities or invoking interfaces:
- Extraction: Parse
signaturesfrom the document. If missing, flag as unverified or reject based on security policy. - Header Decode: Decode
protectedusing base64url and inspectalgandkid. Reject algorithms that do not meet your organization's minimum security baseline (e.g., discard insecure algorithms likenoneor deprecated RSA key lengths). - Key Retrieval: Fetch the corresponding public key referenced by
kid. Verify that the domain of the key URL matchesprovider.urldeclared in the card. - Digest Validation: Reconstruct the unsigned payload, compute the canonical digest, and verify the cryptographic signature against the public key.
Key rotation and security checklist
Follow these operational guidelines when managing Signed Agent Cards in production:
- Decouple Key Lifecycle from Deployments: Use key IDs (
kid) referencing a JWKS URL (/.well-known/jwks.json) rather than hardcoding static keys. This allows seamless zero-downtime key rotation. - Enforce HTTPS Everywhere: Every interface listed under
supportedInterfacesand every key endpoint must mandate HTTPS with valid TLS certificates. - Bind Domain to Provider: Ensure the domain hosting the Agent Card strictly matches the
provider.urland the origin of the JWKS endpoint. - Pre-flight Audit: Always run your completed card through the validator before publishing to your public root.
Perguntas frequentes
Are JWS signatures mandatory for an Agent Card in A2A v1.0?
They are optional in the baseline schema but strongly recommended for any production agent exposed across public or multi-tenant networks. Without signatures, client agents cannot verify whether the card was altered in transit.
What is "Agent Card Poisoning"?
Agent Card Poisoning occurs when an attacker modifies an agent.json document (e.g., through DNS cache poisoning, CDN misconfigurations, or man-in-the-middle attacks) to redirect interface endpoints to malicious infrastructure or inject fabricated skill descriptions.
Which cryptographic algorithm is recommended for Signed Agent Cards?
ES256 (ECDSA using P-256 and SHA-256) is recommended for its compact signature size and high performance. RS256 (RSA with SHA-256) is also widely supported for legacy enterprise PKI compatibility.
Where should public keys or JWKS endpoints be hosted?
Public keys should be published over HTTPS under the provider's authoritative domain, commonly at /.well-known/jwks.json, or referenced via the kid / x5u headers in the JWS protected header.
How does the Agent Card validator evaluate signatures?
The validator verifies that the signatures array contains well-formed JWS compact or flattened objects with valid protected headers, standard algorithm identifiers, and verifiable key references.
Referências
Ferramenta relacionada: Agent Card Validator
Próximo passo
Test your Agent Card integrity before deployment.
The validator inspects your card structure, checks JWS signature formatting, and flags missing public key references or unencrypted interface endpoints.