Security
Threat model
What a verification proves and what it does not, which values are public, and the attacks the protocol is designed to resist.
The claim
This request was signed by a recognized agent credential.
That is the whole statement. A VERIFIED result means someone holding the private key for an active, registered key of a non-disabled agent signed this specific challenge within its validity window. For web_bot_auth, it means the request was signed by a key listed in the named origin's key directory.
What is not proven
- That no human was involved. A human can run, script or supervise an agent, or paste a challenge URL into a terminal. The signature proves possession of a key, not the absence of people.
- That the agent is benevolent, accurate or safe. Registration does not vet behaviour.
- That access should be granted. The token identifies a credential; your site decides what that credential may do. There is no authorization delegation in the protocol.
- Who the operator is. Agent names and websites are self-declared by the registering account.
- Anything about the agent's later requests. The token covers one challenge; it is not a session.
Public values
| Value | Public? | Why it is safe |
|---|---|---|
| Site key | Yes | Only creates challenges, and only from allowed origins. Possession grants nothing else. |
| Challenge id | Unguessable | 128 bits of entropy; acts as the handle for polling. Knowing it lets you observe the outcome and submit proofs, not forge them. |
| Nonce | Yes | A freshness value bound into the signature. Its secrecy is not required. |
| Public keys | Yes | Registered for verification; served in JWKS for the token signing key. |
| Token | Bearer | Anyone holding it can present it once. Send it only to your own backend over HTTPS. |
| Registration token (reg_…) | Secret | Single-use, 15-minute credential that binds a key to an agent. Only its hash is stored. |
| Private keys | Secret | Never leave the agent. |
Attacks considered
| Attack | Mitigation |
|---|---|
| Forging a verified widget state | The widget only reflects server state; hosts must verify the token server-side. |
| Replaying a proof | Challenges are consumed on first valid proof; created must be within ±60 s; the base binds challenge_id and nonce. |
| Replaying a token | Tokens are single-use with consume: true; exp is 10 minutes; jti is unique per challenge. |
| Using a token on another site | aud is the site key; verify with site_key. Origin is embedded in the token. |
| Creating challenges from a phishing page | Origin header is required and must match https://<domain>, https://*.<domain> or an allowed origin. |
| Brute-forcing signatures | Ed25519 (128-bit security); 10 attempts per challenge; per-network, per-site and per-agent rate limits. |
| Flooding mint, verify, registration or health | 429 RATE_LIMITED with Retry-After. Budgets are per address, per site and per agent. A verified credential does not raise them. |
| Tampering with challenge fields | The server rebuilds the signature base from its own record; client fields are only used to look up the challenge. |
| Substituting the signing algorithm | Only Ed25519 proofs and EdDSA tokens are accepted; alg is fixed in both. |
| Using a revoked or pending key | Key and agent status are checked on every proof: KEY_REVOKED, KEY_PENDING, AGENT_DISABLED, AGENT_REVOKED. |
| Passing a test token as live | Test tokens are rejected unless allow_test is set explicitly. |
| Directory spoofing (Web Bot Auth) | https only, no redirects, public hosts only, size and count limits, thumbprint-based key selection. |
Out of scope
- Compromise of the agent host: whoever controls the private key is the agent.
- Compromise of the host page: a malicious script can hide the widget or forward the token elsewhere. Server verification limits the damage to one use.
- Denial of service beyond the documented rate limits.
- Reputation, behaviour analysis and scoring: not part of the product.