State of AI Agent Security · Issue 4 · August 16, 2026

Agent identity is self-asserted. Behaviour is the only signal that is not.

Building a sensor is the easy half. The hard half is working out which of the traffic it records is worth a sentence, and we are still bad at it. This issue reports what changed on the fleet in the last four weeks, re-measures six readings we published earlier, and states the limit we ran into that no further instrumentation removes: agent identity is something a client says about itself.

Of the six re-measurements, one reproduced exactly and five came back different, each with the query that produced it. We are not claiming the figures here are final. The methods are still being built. Every figure here carries the query behind it. Being exact about what that buys you today: the store those queries run against is not open, so a query here shows precisely what was counted, over which population and under which predicate, and lets you challenge a definition or catch a wrong one. It does not let you reproduce a value, because the store is not open. The queries are an audit trail rather than a reproduction recipe, and we would rather say so than let the word check do work it has not earned. Where we hold something back it is on the detection side rather than the measurement side: what our sensors look for stays internal, because a published evasion map helps an attacker sooner than it helps a defender.

Measured July 22 to August 16, 2026. Re-measurement pass ran 2026-08-17, 01:00 to 01:40 UTC, against the research registry. Read time 14 min.

Filtering is the work

Honeypot telemetry is mostly noise, and that is a property of the instrument rather than a defect in it. A public endpoint is reached by search crawlers, security scanners, link prefetchers, uptime monitors and the operator's own tests continuously, and by anything worth writing about rarely. Standing up a sensor takes a week. Learning what its output means takes months, and every month so far has turned up at least one population we were counting as signal.

We also go slower than we could on purpose. A published claim about traffic is a published claim about somebody, and an inference about who was driving a request is the kind of claim that carries consequences past an inaccurate chart. That constraint is real and it shapes what we are willing to assert from a user-agent string.

Four things this month cost us time, and all four are portable to anyone building the same kind of telemetry:

  • You cannot subtract yourself until you know all your own names. Our probes answer to twelve user-agent labels. The filter that produced a published figure knew one of them.
  • Distinct counts do not add. A published address count was the sum of six per-group distinct counts. Summed it reads 395, over the union it is 302.
  • A zero can mean the check never ran. Two thirds of one population was unclassified because the classifier was not deployed to those surfaces, which is a different fact from a classifier that found nothing.
  • There is always another layer. Removing the largest noise source revealed a residual that is itself roughly 30 percent our own smoke test and declared scanners.

What we have applied so far is narrow, and worth stating precisely so it is not mistaken for a process fix. Every figure in this issue was re-run against the live store rather than carried forward from a note, each one is published with its query, and one drafted item was held back because it could not be reproduced. The durable versions of these lessons, a registry of our own probe identities with a check that fails on an unregistered one, and an audit of every published per-group unique count, are open work and not done. The process for challenging any of this is on the methodology page.

The calibration record

Reading 1 of 6
Confirmed
Prior reading
Of the 1,994 payload callbacks we could attribute since agent-vs-crawler attribution shipped on July 16, 1,985 resolved to browsers and link prefetchers, 9 to declared LLM content crawlers, and 0 to autonomous LLM agents.
Issue 3, July 22, 2026
On the instrumented fleet

Reproduces exactly. At a cutoff between 20:00 and 23:00 UTC on July 22, the day Issue 3 went up, the same query returns 1,985 browsers, 9 crawlers, 1,994 attributed in total, and no autonomous-agent row at all. The split covered 1,994 of the 2,076 callbacks recorded in that window, which is 96.1 percent of it.

What changed, and why it matters

This figure was re-checked first because a direct query looks like it disagrees with it. Asking the database for event_type = 'payload_callback' returns 64 rows against the 1,994 Issue 3 describes, a 31-fold gap. The two are counting different event classes: the surface card labelled Payload Callbacks is fed by the database event canary_triggered, and the card labelled Canary Triggers is fed by payload_callback. The mapping is deliberate and written into the repository, which is the only reason the gap could be resolved rather than argued about. We are naming it here so that anyone querying our data directly knows which class to ask for, and we are treating documented field mappings as part of the published methodology from now on.

What was counted
SELECT agent_type, count(*) FROM agentpwn_interactions WHERE event_type = 'canary_triggered' AND created_at >= '2026-07-16' AND created_at < '2026-07-22 21:00+00' GROUP BY 1
Reading 2 of 6
Refined
Prior reading
Our own verification probes accounted for seven percent of the bait follows: 970 rows, but only 13 addresses out of 9,300.
Internal measurement note, August 2026
On the instrumented fleet

972 rows and 14 addresses out of 9,433, which is 7.12 percent. The direction and the magnitude hold. What does not hold is the method: the filter that produced the original figure matched one probe name, and our probes answer to twelve.

What changed, and why it matters

The earlier figure filtered on DIAG-VERIFY-TEST-PROMISE, the name our release-gate probe uses. Our other probes use different names. Widening the filter to any user agent containing DIAG finds eleven further labels, among them DIAG-POST-MERGE, DIAG-HDRCHK, DIAG-PROBE-STATUS and five variants of DIAG-APEXFIX. The percentage barely moves, which is the reassuring part. The method is the part worth taking: subtracting your own traffic requires an inventory of every name your own tooling answers to, and a filter written against one of them will quietly under-count. We do not have that inventory as a maintained artefact. We have a list of twelve names found by widening a filter once, which is not the same thing, and building the registry plus a check that fails when an unregistered probe name appears is open work rather than something this issue can report as done. Every team running agent telemetry has this problem and most have not gone looking for it.

What was counted
SELECT count(*) FILTER (WHERE user_agent LIKE '%DIAG-VERIFY-TEST-PROMISE%') AS one_name, count(*) FILTER (WHERE user_agent ILIKE '%diag%') AS any_name, count(DISTINCT user_agent) FILTER (WHERE user_agent ILIKE '%diag%') AS names FROM agentpwn_interactions WHERE event_type = 'canary_triggered'
Reading 3 of 6
Refined
Prior reading
Strip the registry crawler and the entire remaining JSON-RPC surface is 574 events from around 380 addresses.
Internal measurement note, August 14, 2026
On the instrumented fleet

595 events from 302 distinct addresses. The event count grew because the sensor kept running. The address count fell because the original was not a count of addresses.

What changed, and why it matters

The address figure came from adding up the per-action address counts in a six-row breakdown: 113 plus 178 plus 56 plus 34 plus 1 plus 1 gives 383. That sum is not the distinct count of the union, because an address that appears under two actions is counted twice. On the same rows today the sum gives 395 while the union is 302, so the sum runs about 31 percent high. The data and the filtering were both sound. The figure in this issue is taken over the union directly. The same error is available to any per-group breakdown of unique clients, which is a common shape in honeypot reporting, and we have not yet audited our other published breakdowns for it.

What was counted
SELECT count(*) AS events, count(DISTINCT source_ip_hash) AS addresses FROM honey_events WHERE event_type = 'mcp_connection' AND metadata ? 'action' AND coalesce(metadata->>'userAgent','') <> 'mcpregistry-bot/0.1'
Reading 4 of 6
Refined
Prior reading
99.29 percent of the honest subset is a single address running a registry crawler. What is left is the honest subset.
Internal measurement note, August 14, 2026
On the instrumented fleet

The crawler share is now 99.31 percent, and the remainder is not an honest subset either. At least 175 of the 595 residual events, 29.4 percent, are our own smoke test or a self-identified scanner. The single largest user agent in the residual is a mobile Safari string.

What changed, and why it matters

Removing the registry crawler was treated as the final filtering step. It is better understood as one layer of several. Of the 595 events that survive it, 176 carry an iPhone Safari user agent across 145 addresses, 110 are CensysInspect, 40 across 23 addresses are our own TrapMyAgent smoke test, 24 are Amazonbot, one is Googlebot, and 142 carry no user agent at all. This is the ordinary shape of a public endpoint: crawlers, scanners, prefetchers and monitoring reach it continuously, and each are removed by a different rule. Publishing the layers rather than a single filtered number is what lets a reader judge where to stop. Beneath all of them, 22 tool calls in 98 days is the figure that has not moved since it was first recorded.

What was counted
SELECT metadata->>'userAgent' AS ua, count(*), count(DISTINCT source_ip_hash) FROM honey_events WHERE event_type = 'mcp_connection' AND metadata ? 'action' AND coalesce(metadata->>'userAgent','') <> 'mcpregistry-bot/0.1' GROUP BY 1 ORDER BY 2 DESC
Reading 5 of 6
Refined
Prior reading
Every APT cluster in the data resolved to a continuous integration job: 61 events, 17 sessions, all of them ours.
Internal measurement note, August 2026
On the instrumented fleet

20 sessions, not 17, and the 61 is one of two different quantities that were being reported as one. 61 deep-engagement events across 20 sessions, every one ours. 41 of those crossed the APT threshold, across the same 20 sessions, every one ours. The finding is unchanged and the count moved while nobody was looking at it.

What changed, and why it matters

Two distinct measurements were being quoted as one number. The deception ladder's second rung, deep_engagement, has fired 61 times. The APT score has crossed its 0.4 threshold 41 times. Both resolve entirely to sessions whose identifier begins smoke-e2e, our own end-to-end test, and both fall in the same six days of early August. The session count reads 20 today rather than 17 because the test kept running, which is the expected behaviour of a live counter and the reason every figure in this issue carries a timestamp. The finding underneath is unchanged and is the one that matters: on real traffic the APT score has never crossed the threshold. Its ceiling across 933,941 scored events is 0.294118, and exactly one event has ever reached it.

What was counted
SELECT count(*) AS events, count(DISTINCT session_id) AS sessions, count(*) FILTER (WHERE session_id LIKE 'smoke-e2e-%') AS ci_events FROM honey_events WHERE apt_score >= 0.4
Reading 6 of 6
Refined
Prior reading
Two thirds of one population turned out never to have been classified at all, which is indistinguishable from a zero unless you go and look.
Internal measurement note, August 2026
On the instrumented fleet

Still true of the AgentPwn callbacks, where 9,153 of 13,644 rows, 67.1 percent, were never classified. No longer true of the honey fleet, where classification has covered 100 percent of events since the week of July 27. That sensor now reports its first agent-attributed traffic: 6,665 events, 1.98 percent. Every row of it is self-declared.

What changed, and why it matters

A schema change on August 6 promoted agent attribution out of a JSON blob into real columns, and honey-fleet coverage went from 11.82 percent to complete. The zero became a number: 6,665 events attributed to autonomous agents, 6,318 claiming OpenAI across 135 addresses, 345 claiming Perplexity, 2 claiming Anthropic. The distinction that matters is what claiming means. Every one of those rows carries a confidence of claimed rather than verified, and that is a meaningful distinction rather than a missing feature: verified is a value this sensor issues, it has issued it 1,921 times, and every one went to a declared content crawler whose identity can be checked against published address ranges. No agent-attributed row has been verified yet. So the correct reading is that agent traffic has appeared to the extent that clients declare it, and declared identity is a different thing from verified identity, which this sensor can produce for one class of client and not for this one.

What was counted
SELECT agent_type, attribution_confidence, count(*) FROM honey_events WHERE created_at >= '2026-07-27' GROUP BY 1, 2 ORDER BY 3 DESC

The noise floor, in full

Each row removes one identifiable population from the row above it. We publish the layers rather than a single filtered total, because which layer you stop at depends on the question you are asking, and a reader cannot judge that from a finished number. This is the shape of a public endpoint under continuous automated traffic.

events recorded by the honey fleet934,280
MCP connections884,154
connected and asked nothing798,182
carried a JSON-RPC action85,972
one registry crawler, one address85,377
everything else595
our own smoke test, inside that residual40
actually called a tool, in 98 days22

The ratio from the first row to the last is 42,467 to 1. Any team standing up agent telemetry should expect a figure of that order, and should expect most of the work to be in the subtractions rather than in the collection. Twenty-two tool calls is the figure that has not moved since it was first recorded, across 98 days.

Declared identity is now measurable, and it is not the same as verified identity

A schema change on August 6 moved agent attribution out of a JSON blob and into real columns on the honey fleet. Coverage on that sensor went from 11.82 percent of events to all of them in a week. The agent bucket is no longer empty, and the sensor can now record how confident it is in each row. This applies to the honey fleet only. The AgentPwn side is covered further down and is mostly not instrumented.

unknown, the classifier ran and could not tell73.35%246,487
browser12.37%41,570
declared content crawler7.44%24,989
generic HTTP client4.86%16,330
autonomous agent, self-declared1.98%6,665

All 336,041 honey-fleet events since 2026-07-27, when classification reached full coverage.

The agent bucket holds 6,665 events, which is 1.98 percent. 6,318 claim OpenAI across 135 addresses, 345 claim Perplexity across 16 addresses, 2 claim Anthropic across 2 addresses. The word claim is carrying the entire finding.

Every one of those 6,665 rows has a confidence of claimed rather than verified. That is not a limitation of the field. Verified is a value this sensor issues, it has issued it 1,921 times, and every single one went to a declared content crawler whose identity can be checked against published address ranges. The number of agent-attributed rows that have ever been verified is 0.

So the reading is that agent traffic has appeared to the extent that clients declare it. That is a real measurement and a real improvement on a zero that meant only that we were not looking, and it is not yet evidence that autonomous agents are working these traps. Turning declared identity into verified identity, the way the crawler class already is, is not something this sensor does today.

Why identity cannot answer the question

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 agentthe UA string matches one of five literals: ChatGPT-User, OAI-SearchBot, Claude-User, Perplexity-User, DuckAssistBot
  • declared agent frameworkthe client reported a framework name we recognise
  • declared framework SDK labelthe 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.

What is not instrumented yet, and what that costs

The honey fleet is complete. The AgentPwn side is not, and the shape of the gap is worth stating precisely because the aggregate figure invites the wrong conclusion. Roughly two thirds of the AgentPwn callback population carries no classification, which reads as a classifier that cannot cope. It is not. Over the trailing 14 days, on the one surface where attribution is deployed it resolved 12,081 of 12,111 interactions, which is 99.8 percent. On the 20 surfaces where it is not yet deployed it resolved none of 109,603.

So the unclassified share is a deployment position, not a detection limit. The capability works and has a proven reference implementation. Extending it to the remaining 20 surfaces converts roughly 109,603 interactions a fortnight from unattributable to attributable.

We are stating it this way round because the distinction changes what a reader should do with the AgentPwn figures in Issues 1 to 3. They are sound for the surface they cover and they are not fleet-wide. Read any AgentPwn figure as describing one surface until a coverage percentage is published beside it.

Where planted payloads sit

The crawler holds 272 payloads across 224 sites that we still judge real. Placement is a single value per payload and is set on every row, so these six shares partition the population exactly.

hidden text133 sites156
HTML comment40 sites62
script literal26 sites26
alt or aria text14 sites17
meta tag6 sites6
data attribute5 sites5

Name the population or this chart is wrong. Over every row the crawler has recorded rather than the 272 still judged real, script literals lead at 253 and hidden text is second at 233, which reverses the top of this ranking. The difference is our own false-positive review: 380 rows are marked false positive and 53 are marked removed, and script literals lose far more rows to that review than any other placement. A chart of this data is partly a statement about our detector, not only about where attackers hide things.

We are not publishing a breakdown by industry. The sector field is populated on every row and 256 of 272 rows, 94.1 percent, hold the literal string unknown. A chart of the remaining 16 rows would be a chart of nothing.

Exposure

The August 1 sweep counted 221,215 exposed services against 204,866 on July 22, with the OpenClaw gateway facet moving from 171,036 to 183,737. Read the gateway column and not the total: the Ollama facet moved from 5,126 to 8,447 on an unchanged query, which is the second consecutive month that facet has moved by more than an order of magnitude without any evidence of a real-world change. A port answering a Shodan query is not a running service, and neither number is a count of agents.

Recorded because it is a difference in instrument rather than in the internet: the July sweep ran in count-only mode and the August sweep ran in detail mode. Both totals are read from the same summary field and are comparable.

The limits of this baseline

  • It does not say attackers are absent from the internet. It says 22 honeypots with no reputation and nothing worth stealing recorded 22 tool calls in 98 days. Those surfaces are deliberately unremarkable, which is what makes them a clean noise-floor measurement and what makes them unrepresentative of a real target.
  • It does not retire Issues 1 to 3, and it does not certify them either. One of the six readings reproduced exactly and five moved. We re-ran six figures, not every figure we have published, so the rest are untested rather than confirmed.
  • It does not let you re-run these queries yourself. The research store is not public, so a query documents what was counted rather than enabling reproduction. Treat every figure here as a reading we are accountable for, not one you can independently confirm today.
  • It does not claim the figures here are settled. Every issue so far has revised something from the one before it, this one included, and we have no reason to think that stops. The useful guarantee is not that a number is final, it is that the query is published so the next person to run it can tell us when it moves.
  • It does not include the re-test of the injection census drafted for this edition. No query available to this pass reproduced it, so it was held back rather than published from a note.

Live indices at publication

The numbers in this report are the time-capsule snapshot. The indices below are continuously updated. Click through for the live methodology and CSV exports.