Web Bot Auth
How Web Bot Auth (RFC 9421 HTTP Message Signatures with Signature-Agent discovery) gives PermDock's HTTP adapters a verified agent identity to fill the actor half of the subject.
Draft posture: build (draft-meunier-webbotauth-httpsig-protocol-02; actor.kind: 'web-bot-auth' and the standard apiKey Signature-Agent scheme recipe are the stable twins, per watch list)
What it is
Web Bot Auth is an IETF effort, presented at IETF 126, to let automated HTTP clients (crawlers, agents, bots) prove who they are cryptographically rather than by User-Agent string or IP range:
- Requests carry RFC 9421 HTTP Message Signatures: the client signs selected components of the request (method, authority, path, selected headers) with a private key and sends the signature and its parameters in
SignatureandSignature-Inputheaders. - A
Signature-Agentheader names where the verifier can discover the signer's public keys, so an origin can verify a signature from an agent it has never seen before by fetching the agent's key directory. - The result is a verified, stable identity for the software making the request, independent of any user session it may also carry.
Why it matters for PermDock
PermDock's subject has two halves: principal (the human or service whose grants apply) and actor (the agent making the call). In MCP the actor comes from the OAuth clientId; in the AI SDK from the runtime context; in A2A from the calling agent's card. Plain HTTP has had no equivalent: a request from an agent looks like a request from a browser. Web Bot Auth fills that gap. When an HTTP adapter verifies a Web Bot Auth signature, the signer's identity becomes actor, and policies can distinguish "the user did this" from "an agent did this for the user" without changing the application's authentication. See subject and delegation.
Two things Web Bot Auth does not provide, and PermDock does not infer from it: the principal (that still comes from the session or bearer token) and delegated authority (that comes from scopes or authorization_details). A verified actor with no delegation is an actor with no authority.
How PermDock uses it
import {
createPermDock,
discoverViaSignatureAgent,
verifyWebBotAuth,
} from "permdock/hono";
const keys = discoverViaSignatureAgent({ allow: ["agents.example.com"] }); // key discovery policy
export const { permdock, protect } = createPermDock(policy, {
subject: (c) => c.get("user"),
webBotAuth: (request) =>
verifyWebBotAuth(request, {
keys,
required: false, // unsigned requests are still allowed, with no actor
}),
});
app.use(permdock());
app.delete(
"/posts/:id",
protect(permissions.post.delete, (c) => loadPost(c.req.param("id"))),
handler,
);
// A verified signature sets actor = { id: <signer key id>, kind: 'web-bot-auth' } on the request-scoped PermDockBehaviour in the HTTP adapters:
- Verification is optional and off by default. When
webBotAuthis set to averifyWebBotAuthcall, a request withSignature-Inputis verified against keys discovered throughSignature-Agent, subject to an allow-list of key directories. - Freshness is bounded.
createdis required, may be at most 60 seconds in the future and at mostmaxAgeseconds old (default 300); a pastexpiresrejects.now(Unix seconds) replaces the system clock, which is how the RFC 9421 Appendix B vectors are checked. - Invalid signature fails closed. A request that claims a signature but fails verification is rejected before any permission check, with a Problem Details body of type
.../invalid-signature. It is never downgraded to an anonymous actor. - Verified signer becomes
actor.actor.idis the signer's key identifier andactor.kindis'web-bot-auth'. Policies may referencesubject.actorin conditions (for example, denyingpost.publishwhen any actor is present), andon('decision')events include it for audit. - Delegation still comes from the token. If the same request carries a bearer token with scopes, those fill
delegationexactly as they do for non-signed requests. If it carries none, the subject has an actor and nodelegation, so every check isdeniedwith reasonno-delegation, with or without a session. - OpenAPI. Routes that accept signed agent traffic can be marked so the OpenAPI emitter documents the signature requirement as an
httpsecurity scheme; the exact representation is an open question.
Request lifecycle
- An agent sends
DELETE /posts/p_42with a bearer token for user u_123 and RFC 9421SignatureandSignature-Inputheaders, plusSignature-Agentpointing at its key directory. - The adapter's
permdock()middleware sees the signature, checks theSignature-Agenthost against the allow-list, fetches (or reads from cache) the public key, and verifies the signed components. A key directory fetch is aborted after 5 seconds, and the signature then counts as unverifiable. - On success, the request-scoped
PermDockis built withprincipalfrom the token,actorfrom the signer, anddelegationfrom the token scopes. protect(permissions.post.delete, ...)loads the post and callsdecide. A policy that deniespost.deletefor anyactor(a "humans only" rule) yieldsdenied; otherwise the normal grant and delegation intersection applies.- The 403 body, if any, is a Problem Details document; the
on('decision')event names the signer asactor.
Policy examples
const member = role("member", [
allow(permissions.post.read),
allow(permissions.post.update, { where: { authorId: principal.id } }),
deny(permissions.post.publish, { to: actor("web-bot-auth") }), // agents may draft, humans publish
]);actor(kind) is a grantee, not a condition: it matches when the subject's actor.kind equals the kind, so a deny with to: actor('web-bot-auth') blocks signed agent traffic whatever principal it carries. A verified actor never becomes a principal; an agent acting on its own behalf authenticates as a principal with kind: 'service' and its own roles (OAuth for agent delegation).
Mapping table
| Web Bot Auth / RFC 9421 concept | PermDock concept |
|---|---|
Signature, Signature-Input headers | Verified by the HTTP adapter when webBotAuth is set |
Signature-Agent key discovery | The keys option of verifyWebBotAuth: a discovery policy with an allow-list of directories |
| Signer key identifier | actor.id |
| Signature scheme | actor.kind: 'web-bot-auth' |
| Unsigned request | No actor; principal from the session as usual |
| Failed verification | Rejected before authorization (fail closed) |
| Verified agent without a token or session | actor set, anonymous principal, every check denied |
| Verified agent with a user token | Two-principal subject; decision = principal grants ∩ delegation |
actor('web-bot-auth') grantee | Policy can distinguish agent-driven from human-driven calls |
| Audit | actor included in on('decision') events and OTel attributes |
Sources
- draft-meunier-webbotauth-httpsig-protocol-02 (HTTP Message Signatures for automated traffic).
- draft-meunier-http-message-signatures-directory-05 (Signature-Agent key directory).
- RFC 9421 (HTTP Message Signatures), referenced by number.
- Product plan, HTTP adapter section ("Optional Web Bot Auth verification (RFC 9421) fills
actorfor agent callers").
Last updated on
A2A
How permdock/a2a emits Agent Card security schemes and requirements per skill and filters the authenticated extended card by the caller's permissions.
Agent docs standards
AGENTS.md, Agent Skills and llms.txt as the formats coding agents read, and what PermDock ships in each so an agent can wire and audit permissions without reading source.