>_ wba.cloudless.sh

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.

demo.mjs
// 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.

covered
("@target-uri" "signature-agent";key="sig1")
@authority instead of @target-uri is accepted; ;key names the Signature-Agent member
tag
"web-bot-auth"
required
keyid
RFC 7638 JWK thumbprint
of a key in your directory
alg
"ed25519"
optional; Ed25519 keys only
created / expires
unix seconds
max age 60 s, skew 5 s
nonce
fresh per request
a replay within the window fails

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.

  1. 01 headers Signature headers

    Signature-Input, Signature and Signature-Agent are present and parse as structured fields.

  2. 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.

  3. 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.

  4. 04 directory_key Key lookup

    The keyid is in the directory, matched by RFC 7638 thumbprint (or kid), and is an Ed25519 key.

  5. 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.

  6. 06 coverage Covered components

    The signature covers @target-uri (or @authority) and signature-agent.

  7. 07 tag_alg Tag and algorithm

    tag="web-bot-auth"; alg, if present, is ed25519.

  8. 08 freshness Freshness

    created and expires are present and current against server time, within the max age and clock skew.

  9. 09 nonce Nonce

    A nonce is present, well formed, and not replayed within the freshness window.

  10. 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.

verified
200
The signature and key material validate, and every policy check passed.
invalid
401
The signature, covered components, key, or freshness checks fail.
unverified
401
Not enough information to decide — no signature headers, the directory could not be fetched or parsed, or the key is not in it.
response
// 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.

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: