Verify and produce Web Bot Auth signatures in Go — cryptographic identity for bots and AI agents, with zero dependencies.
Web Bot Auth replaces spoofable user-agent strings with cryptographic proof: agents sign each request with RFC 9421 HTTP Message Signatures (tag web-bot-auth), publish their public keys in an HTTP Message Signatures directory, and identify the directory via the Signature-Agent header. OpenAI signs Operator requests today; Cloudflare, Vercel, and AWS WAF verify at their edges. This module lets any Go service do the same.
Built and maintained by WebDecoy. Ported from and cross-validated against cloudflare/web-bot-auth — the reference implementation's test vectors run in this repo's CI.
- Zero dependencies —
crypto/ed25519,crypto/rsa, and the standard library only. - Both draft generations — the current
Signature-Agentdictionary form withkey="..."member extraction, and the earlier bare-string form deployed signers still send. - Verification is a verdict, not an error —
no-signature/verified/invalid, designed for detection pipelines where an invalid claim of agent identity is the most interesting outcome. - SSRF-guarded key discovery — https-only, host allowlist (or explicit open mode with a non-global-address dialer), size caps, per-hop redirect re-validation, TTL + stale-while-revalidate caching.
go get github.com/WebDecoy/web-bot-auth
import (
"net/http"
webbotauth "github.com/WebDecoy/web-bot-auth"
)
verifier := webbotauth.NewVerifier(
// Fetch keys only from directories you trust:
webbotauth.WithDirectoryAllowlist("operator.openai.com", ".webdecoy.com"),
// ...or verify anything that signs (guarded dialer): webbotauth.WithOpenDirectories(),
)
func handler(w http.ResponseWriter, r *http.Request) {
res := verifier.Verify(r.Context(), webbotauth.RequestFromHTTP(r))
switch res.Status {
case webbotauth.StatusVerified:
// res.Agent, res.KeyID, res.Algorithm identify the signer.
case webbotauth.StatusInvalid:
// The request *claimed* a signed identity and failed to prove it.
// res.Errors says why. Treat as a detection signal.
case webbotauth.StatusNoSignature:
// Plain traffic.
}
}Behind a TLS-terminating proxy pass the original scheme: webbotauth.RequestFromHTTP(r, webbotauth.WithScheme("https")).
signer, _ := webbotauth.NewSigner(ed25519Key,
webbotauth.WithSignatureAgent("https://mybot.example"), // origin serving your key directory
)
req, _ := http.NewRequest("GET", "https://example.com/page", nil)
_ = signer.SignRequest(req) // sets Signature, Signature-Input, Signature-AgentPublish signer.PublicJWK() in a JWK Set at https://mybot.example/.well-known/http-message-signatures-directory.
| Package | Contents |
|---|---|
webbotauth (root) |
Verifier, Signer, JWK/KeySet, Signature-Agent parsing, directory client |
httpsig |
The RFC 9421 profile Web Bot Auth uses: signature base construction, header parsing, Ed25519 + RSASSA-PSS-SHA512 |
thumbprint |
RFC 7638 / RFC 8037 JWK thumbprints (Web Bot Auth's keyid) |
Implements draft-meunier-webbotauth-httpsig-protocol-00 and draft-meunier-webbotauth-httpsig-directory-00 (June 2026), plus the earlier architecture draft's wire forms for compatibility with deployed signers. The IETF webbotauth working group is active; releases are tagged against draft revisions and this README states the pinned revision.
This is a Web Bot Auth implementation, not a general RFC 9421 library: no response signing, no request-response binding, no Accept-Signature negotiation, no HMAC/ECDSA, derived components limited to the request set. If a future draft needs more, the profile grows with it — the point is that what's here is exactly what the protocol exercises, tested against the reference vectors.
- Directory URLs come from the request under verification — they are attacker-controlled input. The default fetch client is https-only, refuses loopback/private/link-local addresses in open mode, caps responses at 1 MiB, and re-checks policy on every redirect hop. Supplying your own
http.Clienttransfers that responsibility to you. - Verification failures never panic and never error out of
Verify; malformed input from the network is a classification, not an exception. Both parsers are fuzzed in CI. - Replay defense is delegated: signatures carry nonces, and
WithNonceCheckerhands enforcement to your store (memory, Redis, ...). The default accepts any nonce within the signature's validity window, per the draft's baseline.
Apache 2.0. Portions ported from cloudflare/web-bot-auth (Apache 2.0) — see NOTICE. Test vectors copied verbatim from that repository.