What it does
The bot_auth provider verifies cryptographically signed AI agents per the IETF Web Bot Auth pattern. AI crawlers sign each request with an Ed25519 key under RFC 9421 HTTP Message Signatures and advertise their keyid in the Signature-Input header. The gateway looks up the matching public key in its directory and verifies the signature. Agents that pass come through. Everything else gets 401.
Wire shape
GET /article HTTP/1.1
Host: blog.example.com
User-Agent: GPTBot/1.0
Signature-Input: sig1=("@method" "@target-uri" "@authority");created=1700000000;keyid="openai-2026-01";alg="ed25519"
Signature: sig1=:Tcle5Bn3...:
Configuration
authentication:
type: bot_auth
clock_skew_seconds: 30
agents:
- name: openai-gptbot
key_id: openai-2026-01
algorithm: ed25519
public_key: ${OPENAI_BOT_PUBKEY}
required_components:
- "@method"
- "@target-uri"
- "@authority"
- name: anthropic-claudebot
key_id: anthropic-2026-01
algorithm: ed25519
public_key: ${ANTHROPIC_BOT_PUBKEY}
Field reference
| Field | Type | Default | Description |
|---|---|---|---|
agents | list | required | Directory of known agents. Each key_id must be unique. |
clock_skew_seconds | int | 30 | Tolerance for the created and expires parameters. |
agents[].name | string | required | Human-readable agent name. Surfaced in logs. |
agents[].key_id | string | required | keyid parameter the agent advertises in Signature-Input. |
agents[].algorithm | string | required | ed25519 or hmac_sha256. |
agents[].public_key | string | required | Hex- or base64-encoded raw key bytes. |
agents[].required_components | list | ["@method", "@target-uri"] | Signature components every accepted request must cover. |
Verdicts
| Verdict | Action | Cause |
|---|---|---|
| Verified | Allow | Signature valid against an agent in the directory. |
| Missing | 401 | No Signature-Input header. |
| UnknownAgent | 401 | keyid claimed is not in the directory. |
| Failed | 401 | Header parse failure, signature mismatch, expired, or required component missing. |
The denial body is intentionally generic. Detailed reasons land in the structured log under the sbproxy::auth target.
Required components
By default a verifier accepts a signature that covers ("@method" "@target-uri"). That alone prevents replay across different routes. Tighten this when the upstream relies on a specific header:
required_components:
- "@method"
- "@target-uri"
- "@authority"
- "content-digest" # bind the body
- "x-replay-id" # caller-supplied nonce
Limitations
- The OSS directory is inline in YAML. Dynamic directory refresh from a hosted JWKS-shaped document is on the roadmap.
- Verification runs in the synchronous auth phase, before the request body is buffered. Signatures that cover
content-digestfail when the body is non-empty.
See also
- Pay Per Crawl to meter signed crawlers per request.
- RFC 9421, the underlying signature standard.