Introduction
A signed request feels final. The signature checks out against the sender's published key, so the request must be genuine. That conclusion is only half right. The signature proves that the holder of a particular key signed these bytes at some point. It does not show that this is the first time the bytes have arrived, or that the party presenting them is the party that signed them.
That gap is the replay attack: a valid message, captured once and sent again. It is one of the oldest attacks in cryptography, and it matters again now that AI agents and crawlers sign their HTTP requests with Web Bot Auth, a profile of HTTP Message Signatures (RFC 9421). A site that lets verified bots past its rate limits or paywall is granting that access to whoever holds a valid signature, and a captured signature keeps working until it expires.
Three limits make a captured signature useless: a short time window, a nonce and a narrow scope. Signers are free to combine them as they like, and every gap a combination leaves has to be closed by the site that verifies.
What Is a Replay Attack?
A replay attack reuses a legitimate message instead of forging one. The attacker does not break any cryptography and does not need the key. Recording a valid request and sending it again is enough, because the recipient checks whether the message is authentic, and an authentic message is exactly what the attacker holds.
A simple replay attack example: an agent sends a signed request to a shop's API. On the way, a copy of the request ends up in a proxy's log file. Whoever reads that log sends the request to the shop again ten seconds later. The signature still verifies and the key is still the agent's, so the shop cannot tell the second copy from the first unless it checks something beyond the signature.
In a man-in-the-middle attack, by comparison, the attacker sits between the two parties and relays or alters traffic while it flows. A replay can happen long after the original exchange, from a different machine, without touching the original connection at all.
What a Valid Signature Proves
In HTTP Message Signatures, the sender picks parts of the request, called covered components, and signs them together with a few parameters such as the creation time. The verifier rebuilds the same list and checks the result with the sender's public key. In Web Bot Auth, that key sits in a directory at the URL the agent names in its Signature-Agent header, so any site can fetch it without a prior arrangement.
A successful check proves three things. The covered components were not changed after signing, the signature was made with a private key that matches the published directory, and the parameters were signed along with them. It does not prove that the request is new. RFC 9421 says so in its section on signature replay: a captured signature "could be applied to two similar HTTP messages", and a signature over no components at all "could be replayed at will by an attacker".
The current Web Bot Auth draft is just as direct. A valid signature "proves that a holder of a key that URL publishes signed the covered message. The message can still be replayed." The draft also names the weak spot of its own minimum profile: a signature that covers only @authority, which is the site's host name, "verifies against any method, path, or body sent to that authority until it expires".
Whether the request is the one the signer meant to send, here and now, depends on the limits placed around the signature.
Does HTTPS Prevent Replay Attacks?
Only on the wire. TLS encrypts the connection, so someone sniffing a café Wi-Fi network cannot read the signed request in transit. The protection ends where the connection ends: the request is decrypted at the CDN or the origin server, and it is often written to logs, so each of those places holds a complete, valid copy.
TLS 1.3 also has a known gap of its own. Its 0-RTT mode lets a returning client send data in its very first flight, and RFC 8446 states plainly that "TLS does not provide inherent replay protections for 0-RTT data". A server that accepts early data has to deal with duplicates itself.
So HTTPS does not prevent replay attacks on its own. Once the request is decrypted it can be sent again, and a signature carried inside HTTPS needs freshness rules of its own.
Three Limits on a Signature: Time, Nonce and Scope
RFC 9421 offers three tools for replay attack prevention. A signature is only as strong as the combination the signer used and the verifier enforced.
Time: created and expires
Every Web Bot Auth signature carries a created and an expires timestamp. The draft makes both mandatory and recommends that the window be no longer than 24 hours. A verifier rejects a signature whose expires has passed and one whose created lies in the future, with a small allowance for clock skew.
The window is the replay budget. A signature valid for 60 seconds gives an attacker one minute, and one valid for a day gives a day. Shorter is safer, but the draft lists the price: very short lifetimes make signatures fail when clocks drift or signing runs slow.
Nonce: one use only
A nonce in cryptography is a "number used once": a random value the signer puts into each signature so that no two signatures are alike. A verifier that remembers the nonces it has seen rejects the second arrival of any of them. Inside the time window, this is what stops a replay, because a timestamp alone cannot tell the first copy from the tenth.
The verifier pays for this with storage. It has to keep each nonce until its signature expires, and the Web Bot Auth draft points out that rejecting duplicates "requires an atomic check-and-record across the enforcement scope": a site running on ten servers needs one shared memory of nonces, or a captured signature can be spent once on each server. Whether to require a nonce at all is left to each deployment.
A site can also choose the nonce itself. The Accept-Signature header lets a server ask the client for a new signature that includes a value the server picked. RFC 9421 notes that an attacker who is only replaying an old signature cannot produce one with that value.
Scope: what the signature covers
The covered components decide where a captured signature can be reused. A signature over @authority alone is valid on every path of that site. Adding @method and @path ties it to one kind of request on one address, and a Content-Digest of the body ties it to one payload. The narrower the scope, the less a replay can do, even inside the time window.
Nonce vs Timestamp vs Scope
| Limit | What it stops | What it costs | Who sets it |
|---|---|---|---|
| Time window | Replays after the window closes | Failures when clocks drift | Signer sets it, verifier caps it |
| Nonce | Any second use inside the window | Shared state on the verifier until expiry | Signer adds it, or the verifier asks for one |
| Covered components | Reuse on other paths, methods or bodies | Breaks if a proxy rewrites a signed part | Signer chooses, verifier can require more |
A timestamp limits how long a captured signature stays useful, a nonce limits how many times, and the covered components limit where, so a signer who drops one leaves that dimension open.
When an Honest Signer Looks Like a Replay
The minimum profile allows something that surprises many verifiers. A signer can make one signature and use it for a short batch of requests to the same site, for example a check of /robots.txt followed by the page it allows. With only @authority covered and no nonce, nothing in the protocol forbids that, and a large operator may send the batch from several addresses in its cloud at once.
To a verifier, the second copy looks exactly like a replay: the same signature, a second time, possibly from a different network address. A site cannot reject it as an attack without also rejecting honest batching, and it cannot accept it without also accepting a real replay inside the same window. That is the practical cost of a missing nonce.
The draft leaves this to policy. An origin that sees a signature or a nonce used more often than it allows may ask for a new signature and respond with 429 Too Many Requests. An honest agent signs again and carries on, and a replayer holding a captured copy cannot.
How to Prevent Replay Attacks on Signed Requests
For a site or API that accepts Web Bot Auth or other HTTP Message Signatures, these checks close most of the gap:
- Check the time window as well as the signature. Reject expired signatures and ones created in the future, with a skew allowance of seconds, not hours.
- Cap the window you accept. The draft recommends no more than 24 hours and says verifiers should avoid windows "longer than their risk model permits". For anything that costs money or changes state, a few minutes is a reasonable ceiling.
- Keep a replay cache. Store each nonce, or a hash of each signature, until it expires, in one store shared by every server. Then decide what a second arrival means: refuse it, slow it down, or answer 429 and ask for a fresh signature.
- Ask for more scope where it matters. For endpoints that change state, request
@method,@pathand a body digest throughAccept-Signature, so a captured signature cannot be pointed at a different action. - Compare the network each copy came from. The same signature twice within a second from one operator's cloud is probably batching. The same signature minutes later from an unrelated network is a likelier replay. The signature cannot tell these cases apart, but the connection details can, such as the network that owns the address and the client's TLS fingerprint.
- Do not turn one signature into a long session. In the draft's words, "a reused signature already has token semantics until expires". A session cookie issued on the strength of it should live no longer and reach no further than the signature itself.
- Check the key's own dates. Keys in a directory can carry
nbfandexp, and a signature can be mathematically correct while its key has already expired. A verifier that honoursexprejects such a signature, so an operator has to publish the next key before the current one runs out.
The same applies when verifying AI crawlers in server logs: a verified signature names the operator, and the checks above decide whether a particular request gets the trust that operator has earned.
FAQ
Q: What is a replay attack in simple terms?
A: It is sending a captured, valid message again. The attacker does not forge anything or steal a key; they reuse a real request, so the recipient sees a correct signature and accepts it unless it also checks freshness.
Q: Does HTTPS prevent replay attacks?
A: No. HTTPS stops outsiders from reading or changing a request in transit, but the request is decrypted at the server, the CDN and any proxy, and anyone holding that copy can send it again. TLS 1.3 early data (0-RTT) can itself be replayed, as RFC 8446 states.
Q: How does a nonce prevent replay attacks?
A: A nonce is a random value used once. The verifier remembers each nonce until its signature expires and rejects any second arrival, so a captured request cannot be spent twice. The server can also supply its own nonce through Accept-Signature, which an attacker with an old signature cannot answer.
Q: Nonce vs timestamp: which one is needed?
A: Both, for different reasons. A timestamp window limits how long a captured signature is useful, and a nonce limits how many times it can be used within that window. A timestamp alone still allows unlimited replays until expiry.
Q: Is a valid Web Bot Auth signature enough to trust a request?
A: It is enough to know which operator's key signed it. It is not enough to know that the request is new or that it came from that operator's own network. The Web Bot Auth draft says outright that a validly signed message "can still be replayed".
Q: How long should a request signature be valid?
A: The Web Bot Auth draft recommends no more than 24 hours. For anything that costs money or changes data, a window of a few minutes is safer, as long as the clocks on both sides are kept in sync.
Sources
- RFC 9421: HTTP Message Signatures, section 7.2.2 "Signature Replay", IETF.
- HTTP Message Signatures for Bots (draft-ietf-webbotauth-httpsig-protocol-00), IETF Web Bot Auth working group.
- RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3, section 8 "0-RTT and Anti-Replay", IETF.
Want to see what a verifier makes of your agent's signature?
The Agent Verifier checks a Web Bot Auth signature's time window, the key directory behind it and the network it arrived from, and it flags a signature it has already seen, including one that comes back from a different network. For a step-by-step look at your own setup, see how to check your agent's signature.
