Botgate
Botgate is an evidence-first conformance and coverage analyzer for Web Bot Auth and RFC 9421 HTTP Message Signatures.
It answers four separate questions:
- Is the message shaped correctly for a selected protocol profile?
- Does the signature verify with an explicitly supplied key?
- Which request properties are cryptographically bound?
- Does that coverage satisfy the application's declared policy?
Botgate deliberately does not infer authentication from an HTTP status code. A 200 response is not evidence that a server accepted a signature; the resource may simply be public.
Status
This repository implements a focused v0.1:
- current
draft-ietf-webbotauth-httpsig-protocol-00inspection; - the current Cloudflare compatibility profile;
- current dictionary-form and legacy string-form
Signature-Agenthandling; - RFC 9421 signature-base reconstruction for the common request components;
- RFC 8941 canonicalization for selected Structured Field dictionary members;
- Ed25519 signing and verification;
- JWK thumbprint/key selection;
- semantic coverage and configurable policy evaluation;
Content-DigestSHA-256 validation;- JSON output for CI;
- a safe offline mutation matrix.
It does not fetch untrusted key-directory URLs or send active mutations. Those operations require an SSRF-safe resolver and an explicit authentication oracle; pretending that response status alone is an oracle would produce misleading results.
Install
You do not need Rust or Cargo to use Botgate.
On macOS or Linux, install with Homebrew:
brew install kraftaa/tap/botgate
If you already use Python tooling, install the same native Botgate binary on
macOS, Linux, or Windows with pipx:
pipx install botgate
Or with uv:
uv tool install botgate
Inside an existing Python virtual environment, ordinary pip works too:
python -m pip install botgate
These package-manager installs use prebuilt native binaries; they do not compile Botgate or install a Rust toolchain. Standalone archives, a Windows installer, a shell installer, and SHA-256 checksums are also available on the Releases page as fallbacks.
The download links become active when the first public release is published. Until then, authenticated repository collaborators can download private release assets with the GitHub CLI.
Confirm the installation:
botgate --version
botgate --help
End-to-end example
Generate a test key and policy:
botgate init
This creates:
.botgate/
├── private.key # Ed25519 PKCS#8, mode 0600 on Unix
├── public.jwk
├── directory.json
└── botgate.toml
The whole .botgate/ directory is excluded by .gitignore. Botgate never prints the private key. A conformant remote key directory must be served over HTTPS.
Running botgate init --force regenerates public.jwk and directory.json from the existing private key. It never overwrites private.key or an existing botgate.toml.
On Unix, the private key is created atomically with mode 0600. On Windows it is created with create_new but inherits the directory's ACL; restrict access to .botgate/ yourself.
Sign the example request using the current IETF form:
botgate sign examples/request.http \
--agent https://agent.example \
--components @authority,@method,@path,@query \
--output signed-request.http
Inspect declared coverage without loading a key:
botgate inspect signed-request.http \
--config examples/strict-policy.toml
Verify cryptographically using a local JWKS:
botgate verify signed-request.http \
--jwks .botgate/directory.json \
--config examples/strict-policy.toml
Show the offline mutation matrix:
botgate test signed-request.http \
--jwks .botgate/directory.json \
--config examples/strict-policy.toml
Nothing is sent by test. In particular, Botgate never turns a GET into a live POST or DELETE request.
The matrix is generated independently for every signature in the request.
Cloudflare interoperability
The September 2026 working-group draft requires new senders to emit dictionary-form Signature-Agent, keyed by the signature label:
Signature-Agent: sig1="https://agent.example"
Signature-Input: sig1=("@authority" "signature-agent";key="sig1");...
Cloudflare currently documents the older bare-string form. Generate and analyze that form explicitly:
botgate sign examples/request.http \
--agent https://agent.example \
--components @authority \
--legacy-agent \
--output cloudflare-request.http
botgate inspect cloudflare-request.http --profile cloudflare
botgate inspect cloudflare-request.http --profile ietf-draft-00
The second command reports why the same request is legacy rather than conformant for a new IETF-draft sender.
Policy
Policy requirements are application requirements, not claims that every conforming Web Bot Auth signature must cover the same fields.
Without --config, Botgate automatically loads .botgate/botgate.toml from the current directory when it exists; otherwise it uses the same built-in defaults produced by init. Every report names the policy it applied (Policy: in text, policy_source in JSON). In CI, pass --config explicitly so a change to .botgate/ cannot silently alter the gate. Unknown keys and negative time limits are rejected.
[policy]
require_authority = true
require_method = true
require_path = true
query = "required"
body = "ignore"
max_age_seconds = 300
max_lifetime_seconds = 300
allowed_future_skew_seconds = 30
require_nonce = false
Coverage is semantic rather than a flat string check. For example, @target-uri satisfies authority, path, and query coverage, while @path does not cover the query. Body integrity requires a covered Content-Digest (the whole field, or its sha-256 member via ;key="sha-256") whose SHA-256 value matches the body bytes. Covering only another member, such as sha-512, does not count.
Individual @query-param components are reported as partial evidence and do not satisfy a policy requiring the entire query to be bound. @request-target covers path and query for the raw HTTP/1.1 requests accepted by v0.1.
JSON and exit behavior
Use --format json with inspect, verify, or test.
botgate inspect request.http --format json
Exit status is 0 when no error-level findings exist, 1 when conformance, compatibility, crypto, or policy findings fail, and 4 for input or configuration errors detected after argument parsing. Clap uses its conventional status 2 for command-line usage errors. JSON retains independent finding categories and cryptographic/identity states so CI does not need to infer meaning from prose.
Trust model
Verification with --jwks establishes that the supplied key validates the signature. It intentionally reports identity as key_only: loading a local JWK does not prove that an HTTPS Signature-Agent URL published that key. URL attribution requires a safe HTTPS discovery operation and its cache state.
The signing command derives the thumbprint from the private key and refuses to proceed if --jwk names a different key pair.
Similarly, signing Content-Digest binds the digest header. Botgate separately recomputes the digest before reporting body integrity.
Supported signature components
v0.1 reconstructs:
@method@authority@scheme@path@query@target-uri@request-target@query-param;name=...- ordinary HTTP fields
- dictionary field members selected with
;key=...
Unsupported derived components cause verification to fail explicitly rather than being guessed. Input files use a strict raw HTTP/1.0 or HTTP/1.1 request format; malformed control data, folded headers, and ambiguous authorities are rejected before analysis.
Non-goals
Botgate does not impersonate commercial agents, bypass bot protection, solve CAPTCHAs, crawl sites, score “AI readiness,” or replace authorization and bot-management systems.
Development
Rust and Cargo are required only when building Botgate from source or contributing to the project:
cargo build --release
cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo test
cargo deny check
The minimum supported Rust version is 1.86. CI runs the test suite on Linux, macOS, and Windows.
cargo test includes property tests for the HTTP and Structured Fields parsers. Coverage-guided fuzz targets live in fuzz/ and need a nightly toolchain and cargo-fuzz:
cargo install cargo-fuzz
cargo +nightly fuzz run parse_request
cargo +nightly fuzz run parse_signatures
cargo +nightly fuzz run signature_base
See SECURITY.md for the trust model and how to report vulnerabilities. Maintainer release setup is documented in RELEASING.md.
The protocol is still an Internet-Draft. Profile-specific behavior is kept separate in the report engine, and botgate protocol identifies the implemented draft.
References
Metadata
Release files for botgate 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| botgate-0.1.0-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| botgate-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64 | Details |
| botgate-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | Python 3 | none | Linux glibc 2.17+ ARM64 | Details |
| botgate-0.1.0-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details |
| botgate-0.1.0-py3-none-macosx_10_12_x86_64.whl | Python 3 | none | macOS 10.12+ x86-64 | Details |
Total release size: 4.5 MB
Release files / botgate-0.1.0-py3-none-win_amd64.whl
| Download URL | botgate-0.1.0-py3-none-win_amd64.whl |
|---|---|
| Size | 980.9 kB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
e995f03a122df8321cf5dcef01a16fb27f66d9e3ae1497c354767a336e1d89f0
|
|
BLAKE2b-256 checksum How to use checksums |
500df627912c79f98e78a97aab765028a27b5a7f990b695dc01b61e2c0d51dfe
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency logRelease files / botgate-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | botgate-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 909.9 kB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
a4626ef9b9642a460918e6cfba82930ec363274f715a39e4c19f882f1f1c24bc
|
|
BLAKE2b-256 checksum How to use checksums |
d4dd47f674912dd9e226c3cbe2eca2b5475d3c9af5ce5f209beed3fd62a487a1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency logRelease files / botgate-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | botgate-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 870.0 kB |
| Tags | Linux glibc 2.17+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
9ed0cb3a7e7d5e9c9f080f7ee1608561c00b9c81ebf726fc4dbc3695ba1579fb
|
|
BLAKE2b-256 checksum How to use checksums |
a1183867a3a3f141a6efe6b745218ebf7bfdd092e342205b1a3e7a53bd2c982f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency logRelease files / botgate-0.1.0-py3-none-macosx_11_0_arm64.whl
| Download URL | botgate-0.1.0-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 845.7 kB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
c3a3e286f7bdfdbd874ff516702ffa67b650970a6a126f600777c70c5d201133
|
|
BLAKE2b-256 checksum How to use checksums |
64ccb4c83f873265e6b389b4e2da8734ad21a5e86aca5f278c123d7a87a760ea
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency logRelease files / botgate-0.1.0-py3-none-macosx_10_12_x86_64.whl
| Download URL | botgate-0.1.0-py3-none-macosx_10_12_x86_64.whl |
|---|---|
| Size | 894.1 kB |
| Tags | Python 3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
7a12d1fe2eb1f60cf491ad6d2970786bb642e5ef2e3af4d64b6a7fa4b3dbe4f6
|
|
BLAKE2b-256 checksum How to use checksums |
ee5e7dfe9ec88d4eea0a9a60797880ea0e515bf976235e17b1fcf675ef2b570b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency log