Skip to content

· by

On this page

Prove It Here: A Public Web Bot Auth Checker

A signed request flowing into the checker's step-by-step report, each step passing to a verified verdict

The previous post in this series ended on a deliberate line. The edge captured and negotiated Web Bot Auth — it read the signature headers off real traffic, and it answered unsigned requests with an Accept-Signature challenge — but it verified off to the side, because reconstructing an RFC 9421 signature base and checking an ed25519 signature needs crypto and a network fetch that a CloudFront Function can’t do. The forward-looking half of the standard, server-requested signing, worked end to end; and the post closed by imagining a day when a site could say prove who you are or you don’t get in.

This post is that side, built and made public. The verifier that used to run off to the side is now a tool anyone can point an agent at: GET https://wba.cloudless.sh/v1/whoami. Send it a signed request and it returns a step-by-step report of whether your signature verifies, and if not, exactly where it fell down.

What it does

It is a checker, not a gate. Send a signed GET and it resolves the directory from your Signature-Agent origin — any origin, no allowlist — matches the key by RFC 7638 thumbprint, reconstructs the RFC 9421 signature base, verifies the ed25519 signature, and returns a report with an Appendix C.1 verdict: verified, invalid, or unverified. Every independent check runs even after one fails, so a single round-trip shows you every problem — a bad covered-component set, a stale created, a missing key — not just the first.

Two things it deliberately is not:

  • It is authentication, not admission. A verified result tells you who the signature says signed the request — nothing is allowed, trusted, or let in as a result. Identity, not permission; the same line the last post drew, held here on purpose.
  • It is open-world. The companion closed-world route this project also runs (signals.cloudless.sh/v1/signed/whoami) answers “are you an agent we accept?” against an allowlist. This one answers the more neutral question: “who does your signature say you are?” Anyone with a reachable directory gets a verdict.

There’s a sandbox identity baked in so the documented example works with zero setup: the checker publishes the RFC 9421 Appendix B.1.4 ed25519 test key — the one whose private half is printed in the RFC, public on purpose. Sign with it and you get a clean verified without having to stand up a directory first. (It identifies nobody; that’s the point.)

The verification core is the same Node code the earlier post described, now doing the job in the open rather than in a notebook: Cloudflare’s web-bot-auth packages for the signature-base and crypto, with the only bespoke part being the resolver that fetches a directory and matches the key. It’s a sibling of the x402 micro-site — same posture: exercise the protocol in public, participate in the ecosystem, see who shows up.

Why a tool and not just a verdict

The useful thing about an open checker isn’t the verified/invalid word — it’s the report. When you’re writing a signer against a -00 draft whose own reference clients are still shifting, the failures are where the learning is. Covered components that don’t match what the verifier expects; a tag that isn’t web-bot-auth; a signature that’s valid but expired; a key that isn’t in the directory you pointed at. The step report names each of those with the specific reason, so the tool is a debugging surface for your implementation, not a judgment on it.

And because every failed request also comes back with an Accept-Signature challenge stating exactly what the checker wants covered, a signer that only signs when asked can be driven through the whole round-trip against it — which turns out to be the interesting part.

Two checkers meet

The last post caught the REDbot diagnostic completing the challenge-response round-trip against the edge: unsigned request, 403 with Accept-Signature, signed retry, done. The same thing works against this endpoint — REDbot signs challenge-driven rather than proactively, so it’s the one real client that exercises the negotiation path end to end here. That’s the signer side meeting the checker.

More interesting is the checker side. A second public Web Bot Auth verifier — Fingerprint’s — turned up and cross-tested against this one. That’s two independent implementations of the same -00 draft pointed at each other, which is the single most useful thing that can happen to a young spec: it surfaces the places where “conformant” quietly means “conformant to how I read it.” Two showed up here.

Coverage. Our signer, by default, covers ("@target-uri" "signature-agent"). Fingerprint’s verifier rejected that outright: it requires ("@authority" "signature-agent") and refuses @target-uri in @authority’s place. The draft’s example lists @authority, but @target-uri is a strict superset — it pins the authority and the path and query — so refusing it is a reading, not a requirement the text spells out. Our own verifier accepts either. The result is a clean disagreement between two checkers about what a correct signature must cover — exactly the kind of ambiguity worth pinning down in the draft rather than discovering per-implementation. (REDbot, from the other post, sits on the @authority side of the same line, which is what made the contrast legible.)

The directory key id. Once coverage lined up, Fingerprint fetched our directory and rejected it for a different reason: our published key carried no kid. The directory served {kty, crv, x, alg} and nothing else. Our own resolver had never cared — it matches keys by their RFC 7638 thumbprint, computed from the key material, so a kid was redundant and we’d simply never added one. Fingerprint’s verifier treats kid as required and refused the whole directory without it.

Who’s right is arguable: kid is optional in a JWK (RFC 7517), and Web Bot Auth keys are addressed by thumbprint, so requiring one is stricter than the spec needs to be. But the fix costs nothing and the right value is exactly the thumbprint the key is already addressed by — so the directory now publishes kid set to the RFC 7638 thumbprint, which is the same string signers already put in keyid. Changed, deployed, and re-tested the same afternoon. After that, Fingerprint verified our signature against our published directory, and ours verifies theirs: two independent checkers now agree over the same keys, in both directions.

Neither of those was a bug that stopped anything real — they were the seams you only find when a second implementation leans on yours. That’s the whole value of an open checker: it’s not there to grade anyone, it’s there to be leaned on.

Where this sits

Same honesty as the rest of the series, because the point of a checker is undermined if it oversells itself.

What it is: a public, open-world verification surface for a live -00 draft — somewhere to send a signed request and get a precise, step-by-step answer, and somewhere two implementations can meet and find the places they read the spec differently.

What it isn’t: production enforcement. The verification core builds on -00-era, explicitly unaudited research libraries, and runs that way here too — it observes and reports; it doesn’t sit in front of anything deciding who gets in. It isn’t a certification or a registry, and a verified verdict confers nothing beyond “this signature checks out against this directory, right now.” Directory-response signing in particular is still the roughest corner of the ecosystem — implementations disagree on what the directory itself must prove — which is its own post once the dust settles.

The arc of the series has been from fingerprints to fingerprints plus reputation to cryptographic proof. This is a small step past that last rung: once the proof exists, you need somewhere to check it — and somewhere the people building the provers and the checkers can discover, in public, exactly where they disagree. That’s what’s now live at wba.cloudless.sh.

If you’re building a signer, point it at GET https://wba.cloudless.sh/v1/whoami and read the report. If it says invalid, that’s the interesting case — it’ll tell you where.


Part of the bot-detection series: Beyond the User-Agent: Catching Spoofed Bots with JA4 and ASN, Building a JA4 Fingerprint Pipeline on AWS, Building a Trust Layer, and From Inference to Proof: Web Bot Auth in the Wild.