Web Bot Auth · implementation check
who does your signature say you are?
Send one signed request to GET /v1/whoami. The checker fetches your own key directory, verifies the signature, and returns a step-by-step report: what passed, what failed, and why. No registration, no account — any agent with a reachable directory can try.
Status · in progress
The /v1/whoami endpoint is not live yet. This page describes what it will check and how to call it; the example below will work once it is deployed.
Authentication is not admission
Same request, two questions.
There are two whoami routes on cloudless. They verify the same kind of signed request the same way; they differ only in which agents they are willing to look up.
closed world
Are you an agent we accept?
https://signals.cloudless.sh/v1/signed/whoami
Verifies only agents on an allowlist (today, one: https://agent.cloudless.sh). Anything else is an unknown principal. This is admission.
open world
Who does your signature say you are?
https://wba.cloudless.sh/v1/whoami
Verifies any agent whose directory is reachable over HTTPS. A pass grants nothing. This is authentication only.
Try it · demo identity
A passing request in one file.
To see a verified report before hosting anything, sign with the RFC 9421 Appendix B.1.4 Ed25519 test key. Its private half is printed in the RFC, so it identifies nobody; its public key is published at https://wba.cloudless.sh/.well-known/http-message-signatures-directory. The keyid is its RFC 7638 thumbprint, poqkLGiymh_W0uP6PZFw-dvez3QJT5SolqXBCW38r0U.
// npm i web-bot-auth (Node 20+) import { sign, generateNonce } from "web-bot-auth"; import { signerFromJWK } from "web-bot-auth/crypto"; // RFC 9421 Appendix B.1.4 test key — public on purpose, identifies nobody const key = { kty: "OKP", crv: "Ed25519", alg: "EdDSA", d: "n4Ni-HpISpVObnQMW0wOhCKROaIKqKtW_2ZYb2p9KcU", x: "JrQLj5P_89iXES9-vFgrIy29clF9CC_oPPsw3c5D0bs", }; const signatureAgent = 'sig1="https://wba.cloudless.sh";type=directory'; const url = "https://wba.cloudless.sh/v1/whoami"; const req = new Request(url, { headers: { "Signature-Agent": signatureAgent } }); const now = new Date(); const { signature, signatureInput } = await sign(req, { signer: await signerFromJWK(key), // keyid = RFC 7638 thumbprint created: now, expires: new Date(now.getTime() + 60_000), nonce: generateNonce(), target: "@target-uri", }); const res = await fetch(url, { headers: { "Signature-Agent": signatureAgent, "Signature-Input": signatureInput, "Signature": signature, } }); console.log(res.status, await res.json());
The demo identity is not agent.cloudless.sh (our own test agent, whose private key stays private). Change the request after signing — a header, the path — and the report shows which step catches it.
Your own agent
What the signature must carry.
Publish your public key(s) at https://<your-agent>/.well-known/http-message-signatures-directory, send Signature-Agent pointing at that origin, and sign https://wba.cloudless.sh/v1/whoami — the exact URL, query included. The checker rebuilds the target from its public hostname, never from a forwarded Host, and echoes what it rebuilt.
Checked against draft-ietf-webbotauth-httpsig-protocol-00, built on RFC 9421 HTTP Message Signatures.
The checks
Every step, in order.
Independent checks all run, so one round trip shows every problem, not just the first. A step that depends on a failed one is reported as skip, never left out.
- 01 headers Signature headers
Signature-Input, Signature and Signature-Agent are present and parse as structured fields.
- 02 signature_agent Signature-Agent
Dictionary form (sig1="https://…") or the legacy bare string; an HTTPS origin on the default port. The report says which form you sent.
- 03 directory_fetch Directory fetch
GET /.well-known/http-message-signatures-directory on that origin: status, content type, size (64 KB cap), no redirects, timing, and whether the result came from cache.
- 04 directory_key Key lookup
The keyid is in the directory, matched by RFC 7638 thumbprint (or kid), and is an Ed25519 key.
- 05 directory_signature Directory response signature
If the directory response is signed (RECOMMENDED by the draft's Appendix B.1), the signature is validated. Absent or invalid is a warning, never a failure.
- 06 coverage Covered components
The signature covers @target-uri (or @authority) and signature-agent.
- 07 tag_alg Tag and algorithm
tag="web-bot-auth"; alg, if present, is ed25519.
- 08 freshness Freshness
created and expires are present and current against server time, within the max age and clock skew.
- 09 nonce Nonce
A nonce is present, well formed, and not replayed within the freshness window.
- 10 signature Signature
The signature verifies over the reconstructed target. If only freshness failed, this still runs with the clock pinned to created, so you learn whether the signing itself is right.
The report
One verdict, three possible answers.
Verdicts use the draft's own vocabulary. Each step has a status of pass, fail, warn, skip or info.
// 200 — verdict verified (abridged) { "schema": "cloudless.wba_whoami.v1", "profile": "draft-ietf-webbotauth-httpsig-protocol-00", "verdict": "verified", "principal": { "signatureAgent": "https://wba.cloudless.sh", "keyThumbprint": "poqkLGiymh_W0uP6PZFw-dvez3QJT5SolqXBCW38r0U", "principalId": "wba:https://wba.cloudless.sh#poqkLGiymh_W0uP6PZFw-dvez3QJT5SolqXBCW38r0U" }, "request": { "targetUri": "https://wba.cloudless.sh/v1/whoami", "serverTime": "…", "signatureAgent": { "form": "current", "key": "sig1", "uri": "https://wba.cloudless.sh" }, "params": { "covered": ["@target-uri", "signature-agent"], "tag": "web-bot-auth", … } }, "steps": [ { "id": "headers", "status": "pass", "detail": "…" }, … { "id": "signature", "status": "pass", "detail": "…" } ] }
What verified means: your signature verified against your published directory, under draft-ietf-webbotauth-httpsig-protocol-00, at the time stated. Nothing else. The checker does not certify, list, rank, trust, or admit agents, and a pass grants nothing beyond that one response.
Limits and limitations
A research endpoint, said plainly.
- Library. Verification uses Cloudflare's web-bot-auth packages — unaudited research software for a draft that is still changing.
- Directory fetches. HTTPS on port 443 only; private, loopback and other reserved addresses are refused; no redirects; 64 KB cap; a few seconds' timeout. Directories are cached for about a minute, failures for less, and fetches per origin are rate-limited.
- Replay. Seen nonces are remembered in memory per running instance, not shared across instances. The check is real; distributed replay defence is out of scope.
- Capacity. Deliberately small. Under load you may get a 429.
What is logged
Enough to learn from, nothing secret.
Standard CloudFront access logs, and one line per check: the verdict, the failing step, your agent origin, the key thumbprint, the Signature-Agent form, the covered components and the directory-signature status. Never the raw signature or your directory's contents. This traffic is part of independent research on how automated clients identify themselves; it is kept internal, and nothing about individual testers is published.
Other tools
Not the only way to check.
Other public tools for testing a Web Bot Auth setup: