TRENDING
A vintage glass syringe with a metal plunger and orange measurement marks lying on a plain white background
November 9, 2026
How to Stop Command Injection in Python subprocess Calls, Including Git Option Injection and the Windows .cmd Trap
A green valet key and a black ignition key with a remote fob on a white background, the brand emblems blurred
November 9, 2026
Cloudflare’s Trusted Agent Delegation Essay Names 14 Site Problems, but the Identity Standard It Prefers Defines No Delegation, and Its First Shipped Fix Sees Agents Only at Login and Sign-Up
Sepia 1900 postcard photograph of two riders on horses galloping neck and neck at the front of a field on a Hannover racecourse, with the rest of the pack behind them
November 9, 2026
CloudLinux Says an Exploit Hit a CloudLinux 8 Server on November 4 Through Linux Kernel Bug CVE-2026-98276, Fixed Upstream Since September, and Red Hat Still Lists Every Supported RHEL Kernel as Affected
Two yellow commuter trains stand side by side on separate tracks next to a platform in a railway yard, with more tracks running in parallel on both sides and trees in the background
November 9, 2026
How to Run Parallel AI Coding Agents With git worktree and Check Their Branches for Conflicts Before You Merge
Car speedometer dial with a red needle pointing at about 60 miles per hour, or about 100 kilometers per hour on the inner scale
November 9, 2026
METR’s Anthropic Report Turns AI-Led Acceleration Into a 1.9x Slope Ratio That Reproduces From Its Charts, but the Ratio Runs From 1.1x to 2.9x With the Fit Function, and a Second METR Report Says Anthropic’s Public Risk Report Understated the Pace
09 Nov 2026
SXZ.io SXZ.io
  • Home
Search the Site
Popular Searches:
Technology Amazon AI
Recent Posts
Tall orange and white container cranes rising against a clear blue sky at a container terminal
How to Fix Python ModuleNotFoundError and Relative Import Errors, Break Circular Imports, and Stop Local Files From Shadowing the Standard Library
November 8, 2026
The Alcatraz lighthouse at night with beams of light sweeping across the sky above the ruined warden’s house
WIRED’s “Free Chinese AI Models Are Getting Scary Good at Hacking” Rests on Two Cyber Ranges: DeepSeek Is Level With the Leader on One, the GLM It Names Finished Last of Seven, and Three Claude Runs Stopped by Refusals Appear Only in the Chart Data
November 8, 2026
Stepped concrete headquarters building of Crédit Mutuel Arkéa at Le Relecq-Kerhuon in Brittany, seen across a lawn with bare trees and flowering shrubs
Crédit Mutuel Arkéa Says Attackers Reached Its Own Account Through a Service Provider’s Platform; the On-Chain Trail Attributed to the Theft Shows $126 Million in USDC Reaching 17 Wallets in 28 Hours, Most of It Moving On Within Minutes
November 8, 2026
SXZ.io SXZ.io
  • Home

Categories

Articles 290 Posts
News 294 Posts
Learning Hub 263 Posts
Home/Articles/Cloudflare’s Trusted Agent Delegation Essay Names 14 Site Problems, but the Identity Standard It Prefers Defines No Delegation, and Its First Shipped Fix Sees Agents Only at Login and Sign-Up
Articles

Cloudflare’s Trusted Agent Delegation Essay Names 14 Site Problems, but the Identity Standard It Prefers Defines No Delegation, and Its First Shipped Fix Sees Agents Only at Login and Sign-Up

Cloudflare’s Trusted Agent Delegation essay lists 14 problems agents create for sites and prefers Web Bot Auth for identity, but that draft says it defines no delegation, the first shipped fix sees...

November 8, 2026 21 Min Read
18

On November 5, Cloudflare published “Agents aren’t humans (but they kinda are),” a 15-minute essay by Bryan Becker, alongside a product post, “See the agents, set the terms.” The essay lists 14 problems that remotely hosted AI agents create for websites, names a set of standards as the way forward, and announces a Trusted Agent Delegation (TAD) coalition. The next day The Register reported Cloudflare’s CEO telling investors that some customers see agents as “a bane to their business.” I read both posts, the Cloudflare documentation they lean on, the IETF documents behind the standards they name and the coalition’s own site, and I wrote a small verifier to see what a Web Bot Auth signature proves.

Table Of Content

  • What the essay asks for
  • The standards behind it, by status on November 8
  • What Web Bot Auth says it does not do
  • A documentation detail worth knowing
  • The one shipped answer, read against the docs
  • What Cloudflare shipped
  • Reading the commingling claim against its own definition
  • What a verified signature tells a site
  • Step 1: reproduce the draft’s test vector, and look at the docs example key
  • Step 2: a minimal signature covers no action
  • Step 3: a signature bound to one request
  • Step 4: identity claims without a signature and a published key
  • Step 5: the RFC test key, if an operator publishes it
  • Step 6: two signers, an agent and its remote browser
  • What the six steps show
  • Where the missing pieces are being worked on
  • What to do with this
  • If you run a website
  • If you build or operate an agent
  • What to watch
  • Method and limits
  • Reproduce the lab
  • Reading the output
  • Sources

The essay is candid about what is unfinished, and that candor is the most useful thing in it. Cloudflare also sells bot management, and the essay’s last section describes its own products, so I tested the claims against the documents rather than the summaries. Here is what I found:

  • The identity layer the essay prefers, Web Bot Auth, says in its current IETF draft that it “does not authenticate human users, does not provide anonymous authentication, and does not define authorization or delegation.” The working group’s charter also puts authenticating “the end user of a participating client or agent” out of scope.
  • The draft’s Appendix D.1 says: “Delegation and chaining are out of scope for this document and are expected to be specified separately.” The repository issue that raised chaining has been open since May 21, 2025.
  • The one feature Cloudflare ships against the essay’s “traffic commingling” problem, mitigation by Hashed User ID, is in early access for Enterprise Bot Management customers, is built from the login field the site points Cloudflare at, and, according to Cloudflare’s documentation, shows which assistant is acting only on login and sign-up events.
  • Grouped by what each description says the site is missing, seven of the 14 problems need information about the human user. The shipped features show a site which assistants act on an account at login and sign-up, and the signature standard the essay prefers is chartered not to authenticate the user.
  • The “whitepaper” link on the coalition’s website goes to the essay itself, and the only way to take part that the site offers is a subscription form for updates.
  • The example key in Cloudflare’s Web Bot Auth documentation is the test key printed in RFC 9421, whose private half is public. I reproduced the draft’s own test vector with it and checked what a verified signature can and cannot tell a site.

Three earlier posts cover neighboring parts of the same effort: Cloudflare’s post-quantum privacy plan, which includes MOLE; four vendors’ statements that their fetchers may skip robots.txt; and OpenAI’s training-signal draft.

What the essay asks for

The essay’s premise is that a remotely hosted agent is an intermediary, like a proxy or a cloud browser, and that sites can no longer simply block intermediaries once most traffic is agentic. It states one principle, which it calls the Intermediation Principle: “The business relationship between Users and Sites, and each participant’s security responsibilities, must be upheld regardless of intermediation.” It then lists 14 challenges for site owners, from account sharing to user attribution, and observes that in nearly every one, “the Site bears the primary cost.”

The essay does not map its challenges to mechanisms, except for one. I grouped them by what each description says the site is missing. The grouping is mine.

What the site is missing Challenges, in the essay’s words What the essay points to Shipped on November 5
Information about the human user (7) Account sharing; Compliance and illegal activity; Friendly fraud; Identity verification breaks; Risk-based authentication breaks; Unauthorized card users; User attribution “Claims,” for which the essay says “we need mechanisms”; OAuth for account identities; MOLE for user reputation A login and sign-up view of which assistants act on an account (early access)
Which agent this is (4) Identity spoofing; Misalignment; Resource exhaustion; Uncompensated consumption Web Bot Auth for agent identity; MOLE for rate limiting without user identifiers Per-agent analytics and policy: Allow, Block on pages with ads, Block
Both (2) Policy circumvention; Traffic commingling Web Bot Auth plus an identity spectrum from nothing to a unique ID For commingling: mitigation by Hashed User ID (early access)
Neither (1) Human verification is irrelevant The essay argues that passing a CAPTCHA through to the user adds no information Not applicable

The essay says plainly what is still open. On agents that sit behind other agents: “We must still define a mechanism that supports multiple intermediaries.” On claims such as age or cardholder status: “we need mechanisms for Sites to request this information, and for Operators or third parties to provide this information.” On site policy: “there is still not a group focused on the larger need for preference expression mechanisms discussed here.” Against that it sets a sentence that deserves a test: “Fortunately, work is already underway along all of these fronts, and many, if not all, of these can be addressed by applying existing standards.”

The standards behind it, by status on November 8

I looked up each named piece on the IETF datatracker and on the projects’ own sites on November 8. “Working group draft” means an Internet-Draft that a working group has adopted. It is not an RFC and can still change or be dropped.

Piece What the essay uses it for Status on November 8
HTTP Message Signatures (RFC 9421) The signing mechanics under Web Bot Auth, per Cloudflare’s docs Published RFC, Standards Track, February 2024
Web Bot Auth Agent identity, “our preferred method” draft-ietf-webbotauth-httpsig-protocol-01 (October 29, 2026), a working group draft whose authors list Cloudflare and Google; the key directory format is folded into it
MOLE User reputation, and rate limiting without user identifiers draft-jms-mole-architecture-01 (November 2, 2026), an individual submission with no working group
Privacy Pass, including Private Access Tokens Trust without identity; the essay calls MOLE the intended successor to Private Access Tokens RFC 9576 (Informational), RFC 9577 and RFC 9578 (Standards Track)
OAuth Account identities A published family of RFCs
AI Preferences Site policy for agents, only in part Working group drafts: vocab-08 (September 14) and attach-05 (August 19). The essay itself says the group focuses on “preferences for use (such as training, inference, and search).”
WIMSE Listed among the activities Cloudflare says it is bridging draft-ietf-wimse-arch-08 (July 6), a working group draft
Poppy (Personal Agent Protocol) “a notion of a session ID” Self-described draft; its examples show protocol version 0.4

Published standards cover the signature format, Privacy Pass and OAuth. The pieces written for agents, Web Bot Auth, MOLE, AI Preferences and Poppy, are drafts. The essay’s “Claims” section has no row because the essay says a mechanism is still needed. The sentence “many, if not all, of these can be addressed by applying existing standards” is therefore true of the signature format and, as the next section shows, only partly true of what the essay needs.

What Web Bot Auth says it does not do

Web Bot Auth is the part of the stack that matters most for the essay’s agent-identity group, so I read the current draft closely. Its scope section says: “This protocol does not authenticate human users, does not provide anonymous authentication, and does not define authorization or delegation. It does not define how trust is accrued, held, or exchanged, and it defines no mechanism for one origin to convey an opinion about an Agent to another.”

The working group’s charter draws the same line. It lists as out of scope “Authenticating the end user of a participating client or agent” and “Tracking or assigning reputation to particular bots,” and it adds: “This effort will focus on authentication of the agent; authentication of the end user is out-of-scope.” The charter was approved and last updated on October 23, 2025.

Three more passages bear on the essay’s vision:

  • Correlation. The draft’s privacy section says the protocol “enables receivers to recognise requests that carry the same identifier and to correlate them, including across origins,” and that “Agents whose identifier would be associated with an individual human, rather than a service or role, expose that person to the same correlation. This protocol is not suited to such deployments.” The coalition’s site promises users can share “only what the interaction requires, without enabling tracking across sites.” My reading is that user-level context therefore cannot simply ride on the signature identifier.
  • Several signers. The draft lets one request carry several signatures, which is how an agent and the remote browser it uses can both sign. It then says: “An outer signature that covers an inner one is evidence that those bytes, over that set of components, were present. It does not make the inner signature valid, and it does not express authorization, delegation, or consent.”
  • Chaining. Appendix D.1 says delegation and chaining are “expected to be specified separately” and points to issue 27, “Consider describing chaining,” which was opened on May 21, 2025 and is still open. The last comment on the issue, from August 5, 2026, reads: “Chaining has been moved off as it is likely a complement, not core to the protocol.” About multiple signatures it adds: “This is NOT chaining or delegation though.”

None of this contradicts the essay, which calls Web Bot Auth its preferred method for “Agent Identity” and says the multiple-intermediary mechanism is still to be defined. It does narrow what “existing standards” can mean for the delegation question in the coalition’s name.

A documentation detail worth knowing

Cloudflare’s Web Bot Auth documentation, last updated October 8, links two older drafts, draft-meunier-web-bot-auth-architecture-02 and draft-meunier-http-message-signatures-directory-03. The datatracker now shows both as replaced, and the replacement chain ends in the single working group draft above. Anyone implementing from the docs page should read the current draft too.

The one shipped answer, read against the docs

What Cloudflare shipped

The essay’s “What you can do today” section lists three steps: a Personal Assistants category and widget in Security Analytics, per-agent access policies, and, for sites in the Account Abuse Protection early access, a view of which agent made each login or sign-up and a way to “mitigate that account” with a WAF rule on its Hashed User ID “without blocking the Agent for every other legitimate user behind it.” It calls the third “a first, concrete answer to the Traffic commingling challenge.”

Cloudflare’s Account Abuse Protection documentation, last updated November 5, adds the conditions:

  • Availability. “Account Abuse Protection is available in Early Access for any Bot Management Enterprise customer. You can use these features at no additional cost for a limited period until they are generally available.”
  • Where the identifier comes from. “Hashed User IDs are created by encrypting the primary credentials your users provide, converting them into opaque identifiers unique to your zone.” Setup tells Cloudflare “where the user’s identifier, such as an email address or username, appears in each login and sign up request.”
  • Scope. “AI assistant visibility covers login and sign up events only. Requests from assistants that Cloudflare does not recognize appear as ordinary traffic.”

The page does not say how Cloudflare recognizes an assistant. Its verified bots page lists three ways a bot or agent can declare itself: “a cryptographic Web Bot Auth signature, a published IP list with a stable user-agent, or reverse DNS.” So even the attribution of a login to an assistant does not have to rest on the signature standard.

Reading the commingling claim against its own definition

This part is my reading, and I did not have an account to test the feature. The essay defines traffic commingling this way: “When one User abuses a shared Agent for credential stuffing, scraping, or card testing, the Site can detect the abuse but can only block the Agent, which also blocks all of its legitimate Users.” Against that definition, three things stand out:

  • The key is the site’s own account. The Hashed User ID comes from the login field the site configures, not from anything the agent operator or the TAD effort supplies. The docs say the control lets a site act on “one account” without blocking the assistant for everyone else.
  • The account is the lever, not the person behind the agent. The docs say the view shows “which account each assistant acts for.” In an attempt to take over an account, the account is the one under attack, so the control acts on that account and leaves the agent’s other users alone. Nothing in the docs says it tells the site who, among the operator’s users, is attacking.
  • Two of the three named abuses have no login event. Scraping and card testing do not need to sign in, and the docs limit assistant visibility to login and sign-up events. On the docs’ wording, the assistant-to-account view does not cover those requests, so the essay’s description of the problem still applies to them.

That is a real improvement for account-takeover cases, in early access, for one product tier. It is a narrower thing than the essay’s picture of seeing “past the Agent to the User.”

What a verified signature tells a site

The Web Bot Auth draft ships test vectors, so I could check my reading against the standard itself. I wrote a verifier of about 80 lines (wba.py) and a script that runs six experiments (lab.py). Both are printed in full at the end. The verifier implements what the experiments need: the signature base, Ed25519, the key thumbprint, the Signature-Agent dictionary member, created and expires, and the rule that a signature is attributed only to its own covered member. It is a model of my reading of the draft, not Cloudflare’s verifier. I ran it on Python 3.13.14 with the cryptography package, version 50.0.0, on Windows 11, with a fixed clock so the output repeats.

Step 1: reproduce the draft’s test vector, and look at the docs example key

== 1. The draft's own Ed25519 test vector (Appendix E.2.1)
my signature equals the draft's: True
thumbprint of the RFC key equals the docs keyid: True
docs example x equals the RFC test key x: True
{"label": "sig2", "keyid": "poqkLGiymh_W0uP6PZFw-dvez3QJT5SolqXBCW38r0U", "ok": true, "why": "", "signature_agent": "https://signature-agent.test"}

Ed25519 signatures are deterministic, so a match to the last byte means the signature base is built the way the draft builds it. The same run shows something about Cloudflare’s documentation. The public key in its example key directory, which starts JrQLj5P_89iX, is the test key published in RFC 9421 Appendix B.1.4, and the keyid in the example is that key’s thumbprint. The RFC prints the private half next to it. The draft says: “Test keys, including the example keys in [HTTP-MESSAGE-SIGNATURES], MUST NOT be used in production. Verifiers SHOULD reject known test keys when they are detected in key directories or out-of-band configuration.” The docs mark only the example Signature value as illustrative (“purely for illustrative purposes”), so I would not paste the example directory as it stands. I did not test whether Cloudflare’s own verifier accepts that key.

Step 2: a minimal signature covers no action

== 2. The minimal signature (@authority plus Signature-Agent) covers no action
same headers on POST /checkout verify: True
with require @method and @path: required component not covered

The draft’s minimum is @authority plus a Signature-Agent member. The same headers verify on a POST /checkout with a different body, which is the draft’s own warning: “a signature covering @authority alone is reusable against that authority for any method, path, and body until it expires.” A verifier can require more by policy. Mine refused the minimal signature once it was told to require @method and @path.

Step 3: a signature bound to one request

== 3. A signature bound to one request
same request: True
same headers, other path: bad signature (InvalidSignature)
301 seconds later: expired

When the signer covers the method, path, authority, a content digest and its agent URL, the same headers fail on another path and stop working when expires passes. The draft says: “Deployments MUST NOT treat a precomputed Web Bot Auth signature as a long-lived access credential.”

Step 4: identity claims without a signature and a published key

== 4. Identity claims that are not backed by a signature and a published key
copied User-Agent, signatures found: []
URL attached but not covered, attributed to: None
signed by a key nobody published: unknown key

A copied User-Agent string produces no signature, so there is nothing to verify. A Signature-Agent header attached after signing is not attributed, in line with the draft: “A verifier MUST NOT attribute a signature to a member that signature does not cover.” A key nobody published is rejected. This is the part of the essay’s “Identity spoofing” problem that Web Bot Auth solves, for agents that sign.

Step 5: the RFC test key, if an operator publishes it

== 5. The RFC test key, if an operator publishes it
signed with the key printed in RFC 9421: True
verifier that rejects known test keys: known test key

If an operator’s real directory held the RFC test key, anyone could sign as that operator, because the private half is in the RFC. A verifier that follows the draft’s advice and rejects known test keys refuses it.

Step 6: two signers, an agent and its remote browser

== 6. Two signers: an agent and the remote browser it uses
honest agent
   agent True https://agent.alice.example
   browser True https://browser.example
impostor inner signature
   agent False bad signature (InvalidSignature)
   browser True https://browser.example

The browser’s signature covers the agent’s signature fields, as the draft’s Appendix D.2 shows. With an honest agent, both verify. When the inner signature is forged, the inner one fails and the outer one still verifies. Appendix D.2 states the conclusion for its own example: “This records that the remote browser forwarded a request carrying the agent’s signature. It does not say that Alice’s agent authorized the remote browser to act for it.”

What the six steps show

Everything a site learns from a verified request is an operator URL and a key. That answers “which agent is this?” well enough to stop copied user-agent strings. It does not answer “which person is behind it,” “did that person agree to this action,” or “who answers if it goes wrong.” Those are the questions the essay says sites need answered, and the draft says it does not answer them.

Where the missing pieces are being worked on

The essay says the TAD coalition will “examine all existing protocols, and then to bring more well-defined problems or problems to the IETF to work through, as needed,” with side meetings at the IETF and workshops “in the future.” The coalition’s site, trustedagentdelegation.org, calls TAD an “Upcoming Collaboration” that “aims to give sites the context and accountability to welcome agents while preserving relationships with users and limiting the personal information shared.” Its worked example is a returning shopper who changes agents: “The new agent presents proof of delegated access and identifies its operator.” Proof of delegated access is the thing the Web Bot Auth draft defines no mechanism for.

On November 8 the site’s “Read the whitepaper” link pointed to the essay at blog.cloudflare.com, and the only way to take part that the page offered was a form to get “occasional updates.” The essay itself says to “check out this page for more information on how to participate.” I found no charter, member list or meeting date on the site.

Elsewhere in the IETF the datatracker lists individual drafts on acting for a user. One is draft-oauth-ai-agents-on-behalf-of-user-02 (February 27, 2026, since expired), which “extends the OAuth 2.0 Authorization Framework [RFC6749] to enable AI agents to securely obtain access tokens for acting on behalf of users.” Another is draft-mcguinness-oauth-ai-agent-instance-00 (July 5, 2026). I did not evaluate either. They show where one answer to “proof of delegated access” might come from, and both are individual submissions.

Poppy describes a different route. A company publishes /.well-known/poppy.json, the user signs in on the company’s own page through OAuth and chooses the scopes, and the agent then works through a short-lived session token bound to a key only the agent holds. A company can also choose to let the agent sign in with the user’s own credentials. The site calls it a draft and says the authors are “working with design partners to get it to 1.0.” The essay cites it only for “a notion of a session ID.”

What to do with this

If you run a website

  • Treat a verified Web Bot Auth signature as the identity of an operator or service. The draft says it is not a user identity.
  • If you require signatures for actions that matter, require coverage of @method, @path and a body digest, and a short expires. The draft says a signature over @authority alone is reusable.
  • Reject known test keys when you ingest key directories, including the RFC 9421 key that appears in Cloudflare’s docs example.
  • If you are in the Account Abuse Protection early access, treat it as an account-level control for login and sign-up events, and keep agent-level policy for scraping, card testing and everything else.

If you build or operate an agent

  • Sign requests and publish a key directory under a service or role identifier, as the draft’s privacy section asks. Do not reuse example keys.
  • Do not tie one signature identifier to one person. The draft says that exposes the person to correlation across origins.
  • If a site needs user-bound access today, the pattern in the Poppy draft, an OAuth sign-in on the site’s own page with scopes the user approves, keeps the user relationship with the site.

What to watch

  • Whether TAD publishes a charter and members, and whether its “well-defined problems” go to an existing working group or a new one.
  • Whether issue 27 or a separate draft gives delegation and chaining a home, and whether the OAuth-side drafts gain adopters.
  • Whether MOLE gets adopted by a working group, since the essay leans on it for user reputation.
  • Whether Account Abuse Protection reaches general availability, and what it says about how assistants are recognized.

Method and limits

I read the essay and the product post on November 8, 2026, along with Cloudflare’s documentation for Account Abuse Protection, Web Bot Auth, verified bots and AI crawler and agent access policies, the coalition’s website, Poppy’s home page, the Web Bot Auth protocol draft (revision 01, October 2026), the working group charter, RFC 9421, the draft’s repository issue and The Register’s report. Draft and charter status come from the IETF datatracker on the same day. The grouping of the 14 problems is mine, and the three bullets in the commingling section are my reading of Cloudflare’s own words.

I did not have a Cloudflare account, so I did not test Account Abuse Protection, the access policies or Cloudflare’s verifier, and everything said here about them comes from the docs and the posts. I did not assess x402, which the essay cites as an analogy, or the claim that agentic traffic may come to rival human browsing. The lab models my reading of the draft. A different reading of an ambiguous sentence would change a line of output. Draft and docs pages change, so check the current versions before acting.

Reproduce the lab

Put both files in one folder and run python lab.py with Python 3.13 and pip install cryptography. The NOW constant fixes the clock, so the output is the same on every run. I also planted six bugs in the verifier one at a time (no signature check, ignored expiry, ignored required components, trusting unknown keys, skipping the test-key check, attributing to any covered member), and the output changed for each, which is why Step 4 includes an uncovered URL and an unpublished key.

# wba.py
# wba.py - a small Web Bot Auth signer and verifier (RFC 9421 profile) for the lab.
# Standard library plus the "cryptography" package (Python 3.13, cryptography 50).
import base64, hashlib, re
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey

def b64u(b): return base64.urlsafe_b64encode(b).rstrip(b"=").decode()
def unb64u(s): return base64.urlsafe_b64decode(s + "=" * (-len(s) % 4))

def thumbprint(x):  # RFC 8037 Appendix A.3: SHA-256 of the canonical JWK members
    return b64u(hashlib.sha256(('{"crv":"Ed25519","kty":"OKP","x":"%s"}' % x).encode()).digest())

def content_digest(body):
    return "sha-512=:" + base64.b64encode(hashlib.sha512(body).digest()).decode() + ":"

def split_top(s):  # split a structured-field dictionary on top-level commas
    out, depth, quote, cur = [], 0, False, ""
    for ch in s:
        quote ^= ch == '"'
        depth += (ch == "(" and not quote) - (ch == ")" and not quote)
        if ch == "," and depth == 0 and not quote:
            out.append(cur.strip()); cur = ""
        else:
            cur += ch
    if cur.strip(): out.append(cur.strip())
    return dict(m.split("=", 1) for m in out)

IDS = r'"[^"]+"(?:;key="[^"]+")?'  # one covered component identifier

def component(req, ident):
    name = ident.split(";")[0].strip('"')
    if name == "@authority": return req["authority"]
    if name == "@method": return req["method"]
    if name == "@path": return req["path"]
    h = req["headers"][name]
    m = re.search(r';key="([^"]+)"', ident)
    return split_top(h)[m.group(1)] if m else h.strip()

def signature_base(req, sp):
    ids = re.findall(IDS, sp[: sp.index(")") + 1])
    return "\n".join([f"{i}: {component(req, i)}" for i in ids] + [f'"@signature-params": {sp}'])

def merge(req, name, member):
    old = req["headers"].get(name)
    req["headers"][name] = f"{old}, {member}" if old else member

def sign(req, label, key, covered, created, keyid, expires=None, agent_url=None):
    if agent_url: merge(req, "signature-agent", f'{label}="{agent_url}"')
    sp = "(" + " ".join(covered) + f');created={created};keyid="{keyid}";alg="ed25519"'
    if expires: sp += f";expires={expires}"
    sp += ';tag="web-bot-auth"'
    sig = key.sign(signature_base(req, sp).encode())
    merge(req, "signature-input", f"{label}={sp}")
    merge(req, "signature", f"{label}=:{base64.b64encode(sig).decode()}:")

TEST_KEYS = {"poqkLGiymh_W0uP6PZFw-dvez3QJT5SolqXBCW38r0U"}  # RFC 9421 B.1.4 test key

def verify(req, directory, now, require=(), reject_test_keys=False):
    """Check every signature on its own. Returns one result dict per Signature-Input label."""
    sin = split_top(req["headers"].get("signature-input", ""))
    sigs = split_top(req["headers"].get("signature", ""))
    results = []
    for label, sp in sin.items():
        ids = re.findall(IDS, sp[: sp.index(")") + 1])
        kv = dict(re.findall(r';(\w+)=("[^"]*"|\d+)', sp[sp.index(")"):]))
        keyid = kv.get("keyid", "").strip('"')
        r = {"label": label, "keyid": keyid, "ok": False, "why": ""}
        names = {i.split(";")[0].strip('"') for i in ids}
        try:
            if kv.get("tag", "").strip('"') != "web-bot-auth": r["why"] = "wrong tag"
            elif keyid not in directory: r["why"] = "unknown key"
            elif reject_test_keys and keyid in TEST_KEYS: r["why"] = "known test key"
            elif now < int(kv.get("created", 0)): r["why"] = "created in the future"
            elif "expires" in kv and now > int(kv["expires"]): r["why"] = "expired"
            elif not set(require) <= names: r["why"] = "required component not covered"
            else:
                raw = base64.b64decode(sigs[label].strip(":"))
                Ed25519PublicKey.from_public_bytes(unb64u(directory[keyid])).verify(
                    raw, signature_base(req, sp).encode())
                r["ok"] = True
        except Exception as e:
            r["why"] = r["why"] or f"bad signature ({type(e).__name__})"
        member = f'"signature-agent";key="{label}"'  # attribute only to this label's own member,
        if r["ok"] and member in ids:                # and only if the signature covers it
            r["signature_agent"] = component(req, member).strip('"')
        results.append(r)
    return results
# lab.py
# lab.py - what a verified Web Bot Auth signature does and does not tell a site.
import base64, hashlib, json
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from wba import *

NOW = 1_780_000_000  # a fixed "current time" (2026-05-28) keeps the output repeatable
RFC_X = "JrQLj5P_89iXES9-vFgrIy29clF9CC_oPPsw3c5D0bs"  # RFC 9421 B.1.4 test key, JWK "x"
RFC_D = "n4Ni-HpISpVObnQMW0wOhCKROaIKqKtW_2ZYb2p9KcU"  # its private "d", printed in the RFC
DOCS_X = "JrQLj5P_89iXES9-vFgrIy29clF9CC_oPPsw3c5D0bs"  # "x" in Cloudflare's Web Bot Auth docs example
DOCS_KEYID = "poqkLGiymh_W0uP6PZFw-dvez3QJT5SolqXBCW38r0U"  # keyid in that same example
rfc_key = Ed25519PrivateKey.from_private_bytes(unb64u(RFC_D))
DIRECTORY = {thumbprint(RFC_X): RFC_X}  # keyid -> public key, as a verifier would hold it

def make_agent(seed, publish=True):  # a deterministic demo key pair, optionally published
    k = Ed25519PrivateKey.from_private_bytes(hashlib.sha256(seed).digest())
    x = b64u(k.public_key().public_bytes_raw())
    if publish: DIRECTORY[thumbprint(x)] = x
    return k, thumbprint(x)

def req(method="GET", authority="shop.example", path="/", body=b""):
    return {"method": method, "authority": authority, "path": path, "headers": {}, "body": body}

print("== 1. The draft's own Ed25519 test vector (Appendix E.2.1)")
SP = ('("@authority" "signature-agent";key="sig2");created=1735689600;keyid="' + DOCS_KEYID +
      '";alg="ed25519";expires=4889289600;nonce="n9p433xm+NJ3ph3upfBIGmsuwHw387YV7Q/F+6BSpGCVjYCqQw6rznNA8PVVLySrAWsv0hQtFioQb6E1YsauiA==";tag="web-bot-auth"')
EXPECTED = "CGokAoY/FrPpyuzTe1Fb2kLTAH9Idw6qSFd1+D77r1firsMRCxeuLu5e6O+ek3zQgDMAzjiXtLVfqE00xnABCQ=="
v = req("POST", "example.com", "/foo", b'{"hello": "world"}')
v["headers"]["signature-agent"] = 'sig2="https://signature-agent.test"'
mine = base64.b64encode(rfc_key.sign(signature_base(v, SP).encode())).decode()
print("my signature equals the draft's:", mine == EXPECTED)
print("thumbprint of the RFC key equals the docs keyid:", thumbprint(RFC_X) == DOCS_KEYID)
print("docs example x equals the RFC test key x:", DOCS_X == RFC_X)
v["headers"]["signature-input"], v["headers"]["signature"] = "sig2=" + SP, f"sig2=:{EXPECTED}:"
rec = verify(v, DIRECTORY, NOW)[0]
print(json.dumps(rec))

print("\n== 2. The minimal signature (@authority plus Signature-Agent) covers no action")
other = dict(v, method="POST", path="/checkout", body=b'{"item": "phone", "qty": 1}')
print("same headers on POST /checkout verify:", verify(other, DIRECTORY, NOW)[0]["ok"])
print("with require @method and @path:", verify(other, DIRECTORY, NOW, require={"@method", "@path"})[0]["why"])

print("\n== 3. A signature bound to one request")
agent, aid = make_agent(b"agent")
COVER = ['"@method"', '"@path"', '"@authority"', '"content-digest"', '"signature-agent";key="sig1"']
body = b'{"item": "phone", "qty": 1}'
buy = req("POST", "shop.example", "/cart", body)
buy["headers"]["content-digest"] = content_digest(body)
sign(buy, "sig1", agent, COVER, NOW, aid, NOW + 300, "https://agent.example")
print("same request:", verify(buy, DIRECTORY, NOW)[0]["ok"])
print("same headers, other path:", verify(dict(buy, path="/refund"), DIRECTORY, NOW)[0]["why"])
print("301 seconds later:", verify(buy, DIRECTORY, NOW + 301)[0]["why"])

print("\n== 4. Identity claims that are not backed by a signature and a published key")
spoof = req()
spoof["headers"]["user-agent"] = "Mozilla/5.0 (compatible; ExampleAgent/1.0)"
print("copied User-Agent, signatures found:", verify(spoof, DIRECTORY, NOW))
bare = req()
sign(bare, "sig1", agent, ['"@authority"'], NOW, aid, NOW + 300)
bare["headers"]["signature-agent"] = 'sig1="https://famous-agent.example"'  # attached afterwards, not covered
print("URL attached but not covered, attributed to:", verify(bare, DIRECTORY, NOW)[0].get("signature_agent"))
stranger, sid = make_agent(b"stranger", publish=False)
lone = req()
sign(lone, "sig1", stranger, ['"@authority"'], NOW, sid, NOW + 300)
print("signed by a key nobody published:", verify(lone, DIRECTORY, NOW)[0]["why"])

print("\n== 5. The RFC test key, if an operator publishes it")
forged = req("GET", "shop.example", "/admin")
sign(forged, "sig1", rfc_key, COVER[:3] + ['"signature-agent";key="sig1"'], NOW, thumbprint(RFC_X), NOW + 300, "https://agent.example")
print("signed with the key printed in RFC 9421:", verify(forged, DIRECTORY, NOW)[0]["ok"])
print("verifier that rejects known test keys:", verify(forged, DIRECTORY, NOW, reject_test_keys=True)[0]["why"])

print("\n== 6. Two signers: an agent and the remote browser it uses")
browser, bid = make_agent(b"browser")
impostor, _ = make_agent(b"impostor")
INNER = ['"@method"', '"@authority"', '"@path"', '"signature-agent";key="agent"']
OUTER = ['"@method"', '"@authority"', '"@path"', '"signature-agent";key="browser"', '"signature-agent";key="agent"',
         '"signature-input";key="agent"', '"signature";key="agent"']
for who, inner_key in (("honest agent", agent), ("impostor inner signature", impostor)):
    r = req("GET", "origin.example", "/resource")
    sign(r, "agent", inner_key, INNER, NOW, aid, NOW + 300, "https://agent.alice.example")
    sign(r, "browser", browser, OUTER, NOW + 1, bid, NOW + 300, "https://browser.example")
    print(who)
    for x in verify(r, DIRECTORY, NOW + 2):
        print("  ", x["label"], x["ok"], x.get("signature_agent") or x["why"])

Reading the output

It prints:

== 1. The draft's own Ed25519 test vector (Appendix E.2.1)
my signature equals the draft's: True
thumbprint of the RFC key equals the docs keyid: True
docs example x equals the RFC test key x: True
{"label": "sig2", "keyid": "poqkLGiymh_W0uP6PZFw-dvez3QJT5SolqXBCW38r0U", "ok": true, "why": "", "signature_agent": "https://signature-agent.test"}

== 2. The minimal signature (@authority plus Signature-Agent) covers no action
same headers on POST /checkout verify: True
with require @method and @path: required component not covered

== 3. A signature bound to one request
same request: True
same headers, other path: bad signature (InvalidSignature)
301 seconds later: expired

== 4. Identity claims that are not backed by a signature and a published key
copied User-Agent, signatures found: []
URL attached but not covered, attributed to: None
signed by a key nobody published: unknown key

== 5. The RFC test key, if an operator publishes it
signed with the key printed in RFC 9421: True
verifier that rejects known test keys: known test key

== 6. Two signers: an agent and the remote browser it uses
honest agent
   agent True https://agent.alice.example
   browser True https://browser.example
impostor inner signature
   agent False bad signature (InvalidSignature)
   browser True https://browser.example

The first three lines of Step 1 are the checks that tie the lab to the standards: my signature equals the one in the draft’s Appendix E.2.1, and the key in Cloudflare’s docs example is the RFC 9421 test key. Everything after that is the verifier’s behavior under my reading of the draft.

Sources

  • Cloudflare, Agents aren’t humans (but they kinda are), November 5, 2026, and See the agents, set the terms.
  • Cloudflare docs: Account Abuse Protection, Web Bot Auth, Verified bots and AI Crawler and Agent access policies.
  • Trusted Agent Delegation and Personal Agent Protocol.
  • IETF: draft-ietf-webbotauth-httpsig-protocol-01, the Web Bot Auth charter, RFC 9421, RFC 9576 and the draft’s issue 27.
  • The Register, November 6, 2026, on Cloudflare’s third-quarter earnings call.

Tags:

AI AgentsBot ManagementCloudflareHTTP Message SignaturesIETFWeb Bot Auth

Share

Sepia 1900 postcard photograph of two riders on horses galloping neck and neck at the front of a field on a Hannover racecourse, with the rest of the pack behind them
Previous Post

CloudLinux Says an Exploit Hit a CloudLinux 8 Server on November 4 Through Linux Kernel Bug CVE-2026-98276, Fixed Upstream Since September, and Red Hat Still Lists Every Supported RHEL Kernel as Affected

A vintage glass syringe with a metal plunger and orange measurement marks lying on a plain white background
Next Post

How to Stop Command Injection in Python subprocess Calls, Including Git Option Injection and the Windows .cmd Trap

No Comment! Be the first one.

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Latest
08 Nov
How to Fix Python ModuleNotFoundError and Relative Import Errors, Break Circular Imports, and Stop Local Files From Shadowing the Standard Library
08 Nov
WIRED’s “Free Chinese AI Models Are Getting Scary Good at Hacking” Rests on Two Cyber Ranges: DeepSeek Is Level With the Leader on One, the GLM It Names Finished Last of Seven, and Three Claude Runs Stopped by Refusals Appear Only in the Chart Data
Trending
November 8, 2026
How to Fix Python ModuleNotFoundError and Relative Import Errors, Break Circular Imports, and Stop Local Files From Shadowing the Standard Library
November 8, 2026
WIRED’s “Free Chinese AI Models Are Getting Scary Good at Hacking” Rests on Two Cyber Ranges: DeepSeek Is Level With the Leader on One, the GLM It Names Finished Last of Seven, and Three Claude Runs Stopped by Refusals Appear Only in the Chart Data
November 8, 2026
Crédit Mutuel Arkéa Says Attackers Reached Its Own Account Through a Service Provider’s Platform; the On-Chain Trail Attributed to the Theft Shows $126 Million in USDC Reaching 17 Wallets in 28 Hours, Most of It Moving On Within Minutes
November 8, 2026
How to Stop Command Injection in Python subprocess Calls, Including Git Option Injection and the Windows .cmd Trap
November 8, 2026
Cloudflare’s Trusted Agent Delegation Essay Names 14 Site Problems, but the Identity Standard It Prefers Defines No Delegation, and Its First Shipped Fix Sees Agents Only at Login and Sign-Up
November 8, 2026
CloudLinux Says an Exploit Hit a CloudLinux 8 Server on November 4 Through Linux Kernel Bug CVE-2026-98276, Fixed Upstream Since September, and Red Hat Still Lists Every Supported RHEL Kernel as Affected

Related Posts

Blue-lit server racks in a modern data center, illustrating the compute infrastructure behind the AI boom.
Articles

The AI Boom Is Spending Real Money Before Proving Real Returns

June 7, 2026
Technician working with a laptop beside server racks, representing enterprise AI retrieval infrastructure
Articles

Google’s Agentic RAG Push Makes Enterprise AI Less of a One-Shot Guess

June 7, 2026
A person with a laptop and smartphone, representing digital attention and AI-assisted work
Articles

AI Chatbots Are Making Attention a Design Problem

June 7, 2026
A customer-support representative wearing a headset against a dark studio background.
Articles

The Meta AI Support Hack Was a Plain Old Authorization Failure

June 7, 2026
SXZ.io SXZ.io
  • [email protected]

Categories

Articles
Learning Hub
News

All Rights Reserved by SXZ.io ©2026