Transparent Request Signing (bot-auth)

July 31, 2026 · View on GitHub

Ed25519 request signing for all outbound HTTP requests per RFC 9421 / web-bot-auth profile.

Status

Implemented

Problem

The toolkit library contract section 9 requires HTTP-capable kits to support Ed25519 request signing. bashkit's curl/wget/http builtins make outbound HTTP requests; target servers need cryptographic bot-identity verification.

Design Decisions

  1. Transparent — signing happens inside HttpClient, before every outbound request. No CLI flags, no script changes.
  2. Feature-gatedbot-auth cargo feature; implies http_client. Disabled = zero crypto deps compiled in.
  3. Non-blocking — signing failures (clock errors, key issues) never block the request; it is sent unsigned. Preserves tool availability.
  4. Follows fetchkit — same BotAuthConfig shape, signing algorithm, header format. Reference: everruns/fetchkit/crates/fetchkit/src/bot_auth.rs.

Architecture

BashBuilder::bot_auth(config)HttpClient::set_bot_auth(config) → on every request, after allowlist check, BotAuthConfig::sign_request(method, target_uri) adds Signature + Signature-Input + Signature-Agent headers.

Signing happens in HttpClient at the same layer as the allowlist check. All outbound HTTP paths are covered:

PathSignedHow
HttpClient::request_with_headers (default reqwest)Yesbot_auth_headers() injected before request.send()
HttpClient::request_with_timeouts (per-request timeout)YesSame bot_auth_headers() injection
Custom HttpTransportYesSigning headers merged into HttpTransportRequest.headers before dispatch (see HTTP Transport)
Redirects (manual follow in curl/wget)YesEach redirect is a new HttpClient request, re-signed with the new authority

Every HTTP builtin — curl, wget, http — goes through HttpClient, so no builtin can bypass signing.

API

BotAuthConfig (from_seed, from_base64_seed, with_agent_fqdn, with_validity_secs [default 300], keyid() = JWK Thumbprint), derive_bot_auth_public_key(seed)BotAuthPublicKey { key_id, jwk }, BashBuilder::bot_auth(config): see crates/bashkit/src/network/bot_auth.rs / rustdoc. Consumers use the derived public key to serve the well-known key directory endpoint; typical wiring reads seed + agent FQDN from env vars.

Signing Format

Per RFC 9421 with web-bot-auth tag:

  • Covered components: @method, @target-uri (+ signature-agent when FQDN set)
  • Algorithm: Ed25519 (alg="ed25519")
  • Key identity: JWK Thumbprint (RFC 7638) as keyid
  • Tag: "web-bot-auth"
  • Nonce: 32 random bytes, base64url
  • Timestamps: created (now), expires (now + validity_secs)

Headers Added

HeaderValue
Signaturesig=:<base64url-encoded-signature>:
Signature-Inputsig=("@method" "@target-uri");created=...;expires=...;keyid="...";alg="ed25519";nonce="...";tag="web-bot-auth"
Signature-AgentFQDN (only when agent_fqdn is set)

Dependencies

Feature bot-auth adds: ed25519-dalek 2.x, rand 0.10 (nonce), zeroize 1.x (key zeroization on drop), sha2 (already required for checksum builtins).

Files

FilePurpose
crates/bashkit/src/network/bot_auth.rsBotAuthConfig, signing, key derivation
crates/bashkit/src/network/client.rsHttpClient integration (bot_auth_headers)
crates/bashkit/src/network/mod.rsModule and re-exports
crates/bashkit/src/lib.rsBashBuilder::bot_auth(), public exports

Security

  • Signing key never leaves BotAuthConfig — only the public key is derivable
  • Drop explicitly calls zeroize() on seed bytes before deallocation (TM-CRY-001)
  • JWK Thumbprint uses SHA-256 with canonical JSON member ordering (RFC 7638)
  • Nonce prevents replay attacks
  • Expiry window limits signature validity
  • Signing failures are non-blocking (TM-AVAIL-001)

References