Every attribution figure in this report, and in the three before it, rests on the same thing: a client telling us what it is. That is worth stating in full rather than as a caveat, because it sets a ceiling on what this kind of measurement can establish, and no amount of further instrumentation on our side removes it.
Our classifier has three routes to an agent verdict. All three are declarations:
- declared first-party bot with a user-directed user agent — the UA string matches one of five literals: ChatGPT-User, OAI-SearchBot, Claude-User, Perplexity-User, DuckAssistBot
- declared agent framework — the client reported a framework name we recognise
- declared framework SDK label — the client reported an SDK label we recognise
A client that does not volunteer any of those falls through to the last test, which asks whether the fingerprint looks human, and returns browser if it does. That bucket is not a declaration, and it is also not an answer: an LLM driving a real browser produces a real browser fingerprint, so this bucket means presents as a browser rather than a person was driving. A language model driving Chrome through an extension, a computer-use loop, or a headless session under automation is a browser by every property this test can read. There is no header that distinguishes it, and adding one would only work for clients willing to set it honestly.
The confidence field measures exactly this gap. Across the honey fleet 405,390 rows carry a confidence of claimed and 1,921 carry verified. Every verified row belongs to declared content crawlers, which publish address ranges and reverse DNS, which can be checked against something the client does not control. The number of agent-attributed rows that have ever been verified is 0, and that is a structural result rather than a backlog. Crawlers can be verified because they publish an identity. Agents have no equivalent to publish.
Our own residual carries a clean instance of the problem. The single largest user agent left after the registry crawler is removed is an iPhone Safari user agent, 176 events across 145 addresses, speaking JSON-RPC to a Model Context Protocol endpoint. Whatever that is, it is not a person browsing on a phone. The identity says browser and the behaviour says otherwise, and only one of those two was informative.
This sharpens what Issue 3 reported rather than reversing it. The clients following our injections presented as browsers and crawlers. On the evidence available then and now, we cannot say none of them was a model driving a browser, and we should not have let that read as settled. What we can say is that none of them declared itself an agent, and that the declaration is the only thing identity was ever measuring.
The consequence for anyone building detection on top of this: an allowlist, a user-agent rule, or a registry of known agent identifiers will catch the clients that were already cooperating and nothing else. The signals that survive an uncooperative client are behavioural. What a client asks for, in what order, how fast, whether it follows an instruction embedded in content it was not told to trust. That is slower to build and it does not produce a clean label, and it is the only part of this that an adversary cannot simply assert their way past.
What can be read without a declaration
That leaves an obvious question: if identity cannot be trusted from a client that will not cooperate, is anything readable without it. It is, and the case is not theoretical. AI Browser Guard 0.7.0, a Chrome extension we maintain, identifies Playwright, Puppeteer, Selenium, Anthropic Computer Use and OpenAI Operator driving a browser without asking any of them to identify themselves, from CDP debugger monitoring, V8 stack-trace analysis and environment fingerprinting. Each method is verified against the real framework rather than a mock.
The boundary matters more than the capability, so it is stated with it: detection reaches external CDP frameworks, per-action enforcement does not, because they drive native input the page realm cannot intercept. Against those the honest description is detect, alert, and close the tab, not per-action policy, and the only categorical prevention is a managed-Chrome policy, RemoteDebuggingAllowed=false. It is beta. We are citing it here because it is evidence for the argument above, not because this is the place to describe a tool. The Chrome Web Store listing and the source are both public, on the same principle as the queries above: a claim you cannot inspect is a claim you have to take on trust.
What we do not publish is the decision boundary. These frameworks are open source and their fingerprints are discoverable by anyone willing to look, so stating that detection is possible gives an adversary nothing. Stating the thresholds each layer uses would hand them the evasion map. That is the line we draw generally: the measurement side is published so this work can be checked, the detection side is not.
It also points at the standard we think is missing, which is not the obvious one. A standard requiring agents to declare themselves only formalises the cooperative case, and this section is an argument that the cooperative case was never the problem. The useful standard is the pair. A channel an agent can use to be known reliably, so a site can serve it properly rather than fight it, and a site-side signal for the automation that says nothing at all. A site that can only see declared agents is a site that has been told what to see.