Web Bot Auth
Verify cryptographically signed agent requests (IETF Web Bot Auth, RFC 9421 HTTP message signatures).
Web Bot Auth
Web Bot Auth lets an agent sign every HTTP request with a key it publishes, so your server
can prove who sent it — no shared secrets, no IP allowlists. It's the IETF standard
(draft-meunier-webbotauth-httpsig-protocol) behind signed agents such as OpenAI's ChatGPT
agent. Agentronics verifies it by default.
Setup
Nothing to configure — Web Bot Auth is on in agentronicsMiddleware() and createAgentAuth().
A verified request resolves to:
Signers we don't have a display name for are shown by hostname.
What the agent sends
Signature-Agentnames the signer. Its keys live athttps://signer.example/.well-known/http-message-signatures-directory(a JWK set), or at a JWKS URL when the member has;type=jwks_uri.Signature-Inputlists what was signed. It must cover@authority(or@target-uri) and thesignature-agentmember, and carrytag="web-bot-auth",created,expiresandkeyid— the JWK SHA-256 thumbprint of the signing key.- The legacy form
Signature-Agent: "https://signer.example"is accepted too.
What Agentronics checks
- The signature is tagged
web-bot-auth(other HTTP signatures are ignored, not failed). created/expiresare present, not in the future or past (60 s skew), and the window is at most 24 hours.@authorityor@target-uriandsignature-agentare covered.- The signer is allowed (see below) and is a safe public HTTPS URL.
- The key directory is fetched (cached per its
Cache-Control, failures cached for 5 minutes) and the key whose thumbprint equalskeyidis selected — keyed on the (signer, key) pair. - The RFC 9421 signature base is rebuilt and the signature verified. Supported algorithms:
ed25519,rsa-pss-sha512,rsa-v1_5-sha256,ecdsa-p256-sha256,ecdsa-p384-sha384. - If a
nonceis present it is recorded, and a replay is rejected.
Our verifier is tested against the specification's published Ed25519 test vector.
Options
| Option | Default | Notes |
|---|---|---|
allowedDirectories | 'any' | Restrict to specific signers. Others become signer_not_allowed without a network call. |
clockSkewSec | 60 | Tolerance for created / expires. |
maxValiditySec | 86400 | Longest accepted signature lifetime. |
requireNonce | false | Reject signatures without a nonce. |
replayCache | in-memory | Implement { has(key), add(key, ttlSeconds) } on Redis/KV to share replay protection across instances. |
authority | request host | (request, url) => string — set when a proxy rewrites Host. |
Security notes
- The signer URL is attacker-controlled, so directory fetches are guarded: HTTPS on port
443 only, no credentials, no IP literals or internal hostnames, no redirects, a 3 s timeout
and a 64 KiB cap. On Node the hostname is also resolved and private addresses are refused.
For the strictest posture, set
allowedDirectories. - Behind a proxy that rewrites
Host, setauthority, otherwise valid signatures fail withbad_signature. - Pin your hostname if your server answers for any
Host. A signature is bound to the@authorityit was made for. If your origin accepts arbitraryHostheaders (common for a bare Node/Express server, not for Vercel or Cloudflare, which route by domain), someone could replay a signature captured from another site within its validity window. Return your real host fromauthority— e.g.authority: () => 'shop.example'— and enablerequireNoncewith a sharedreplayCachefor the strongest posture.
Failure reasons
Unverified results carry reason: 'web-bot-auth:<code>': expired, created_in_future,
validity_window_too_long, signature_agent_not_covered, authority_or_target_uri_not_covered,
signer_not_allowed, discovery_failed, unknown_keyid, bad_signature, replayed_nonce,
and parse errors. See Error codes.