The Wardrobe: Fifty-Six Identities in Five Seconds
It asked to be exempted from AI training, then went looking for your SSH private key.
The user-agent string was Mozilla/5.0 (compatible; Applebot-Extended/0.1; +http://www.apple.com/go/applebot). The request was for /.ssh/id_rsa. Then /.aws/credentials. Then /.env, and twelve variants of /.env, and /rclone.conf, and /.git-credentials, and two hundred and eighteen more โ two hundred and twenty-five requests in six seconds, every one of them a 404, against a hostname that does not exist on this network.
That is not a crawler having a bad day. Applebot-Extended cannot make a request at all.
The shape of the thing
| User-agent | Mozilla/5.0 (compatible; Applebot-Extended/0.1; +http://www.apple.com/go/applebot) |
|---|---|
| Requests under that string | 2,318 |
| Source addresses | 23 โ all AS396982 |
| Distinct paths | 233 |
| Target hosts | 17 |
| First seen | 2026-07-19 08:01:17Z |
| Last seen | 2026-08-08 07:31:25Z โ still running |
| robots.txt fetches | 0 |
| Successful responses | 0 |
The hosting is not exotic. AS396982 is Google Cloud customer egress โ the range you get when you rent a VM, not the range Googlebot crawls from. That distinction matters enough that our own ban logic writes it down explicitly: Google's crawler lives on AS15169, and AS396982 is marked as not that. Anyone can rent an address here for the price of a coffee. Nobody inherits Google's reputation by doing so.
And the targets are not the site. They are guesses at what else might be on it: clients.shuffle-on.com (285 requests), qa.shuffle-on.com (221), crm.shuffle-on.com (218), then the real hostnames almost as an afterthought. Fourteen of the seventeen hostnames it tried have never existed. It is enumerating a company it has imagined โ one with a client portal, a QA environment, and a CRM โ and asking each imaginary host for its credentials.
The user-agent that cannot exist
Applebot-Extended is real. It is also, definitionally, not a thing that visits you.
Apple operates one crawler, Applebot, which fetches pages. Separately it publishes a token, Applebot-Extended, whose entire function is to be read โ you write it into robots.txt to tell Apple not to use your already-crawled content for training its generative models. It is a control surface, not an agent. It has no fetcher behind it. It appears in a file on your server and nowhere else.
So a request bearing Applebot-Extended as its user-agent is not a spoof that happens to be wrong. It is a category error. The forger read Apple's documentation, saw two strings in the same table, and could not tell the difference between the crawler and the opt-out flag for the crawler.
There is a second tell in the same string. Apple's genuine Applebot identifies as a Safari build:
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15
(KHTML, like Gecko) Version/17.0 Safari/605.1.15
(Applebot/0.1; +http://www.apple.com/go/applebot)
The impostor uses the (compatible; โฆ) construction โ the shape used by Googlebot and Bingbot, not by Apple. Whoever assembled this took the general form of "a crawler user-agent," dropped in a token harvested from a list, and never checked what Apple actually sends.
It never reads robots.txt
This is the detail that settles it.
Across 2,318 requests from 23 addresses over three weeks, the fleet requested robots.txt zero times.
An agent claiming the identity of a robots.txt directive โ a string that exists for no purpose other than to be read out of that one file โ never once asked for that file. It is wearing the name of a rule while demonstrating it has never read the rules. If there were any remaining doubt that the identity is decorative, this removes it: the thing does not know what its own name means.
Real Applebot, for comparison, requests robots.txt before it crawls, because that is what the identity is for.
Fifty-six identities in five seconds
Filter the logs by Applebot-Extended and you find a crawler-impersonation campaign. Filter by address instead, and you find something considerably more interesting.
At 01:57:27 on 21 July, 34.17.101.240 began a burst. It ended at 01:57:32. Five point zero two seconds. In that window it issued 484 requests under 56 distinct user-agent strings.
The AI-crawler wardrobe, in the order it was worn:
| identity | requests |
|---|---|
Claude-SearchBot | 27 |
Amzn-SearchBot | 28 |
GoogleOther | 26 |
OAI-SearchBot | 23 |
ClaudeBot | 21 |
Claude-User | 21 |
CCBot/2.0 | 21 |
MistralAI-User | 21 |
Applebot-Extended | 21 |
anthropic-ai | 19 |
PerplexityBot | 17 |
cohere-ai | 16 |
Applebot | 16 |
Perplexity-User | 15 |
GPTBot/1.3 | 15 |
Amazonbot | 14 |
ChatGPT-User | 12 |
Bytespider | 11 |
Diffbot | 11 |
Googlebot/2.1 | 10 |
Go-http-client/2.0 | 9 |
Twenty-one bot identities, and then roughly thirty-five browser strings behind them โ Windows Chrome, macOS Safari, Firefox on Linux, an iPad on iOS 17, an iPhone on iOS 26, a Pixel 9, a Samsung SM-S921B, an OPPO CPH2557.
This is not impersonation of Apple. It is impersonation of everyone, cycled at machine speed through a hardcoded list, against a target that could not possibly distinguish them fast enough to matter. Two other nodes in the same fleet did the same thing at smaller scale โ 24 identities and 23 identities, each inside a single burst.
The purpose is not disguise in any conventional sense. Five seconds of traffic from one address wearing 56 names is not trying to look like 56 visitors. It is trying to find out which name gets through. Somewhere there is a filter that lets ClaudeBot past and blocks Go-http-client, and this is the tool that maps it.
The tell it did not think to remove
Ninth in that list, between ChatGPT-User and Amzn-SearchBot, sits Go-http-client/2.0.
That is not a mask. That is the default user-agent of Go's standard net/http package, emitted when nobody sets one. In a carousel of 56 fabricated identities, nine requests went out wearing the truth, because whichever code path issued them forgot to set the header.
Readers of the first letter in this series will recognise the shape of that. The SSH scanner in TI-2026-102A announced itself as SSH-2.0-Go across 683 addresses, and the lesson there was that the fingerprint identified the compiler, not the operator โ go build is not an attribution. The same caution applies here and should be stated plainly: Go-http-client/2.0 tells us this was written in Go. It does not tell us it was written by the same person.
What it does tell us is the same structural fact that made 102A legible. Both tools are Go binaries that leak their toolchain when their disguise slips. In 102A the disguise never slipped, because the tool made no attempt at one โ it simply announced SSH-2.0-Go and went about its business. Here the disguise is elaborate and it slipped nine times out of 484. The operator who built this cared about identity in a way the census scanner did not.
What it actually wants
Strip the costumes and read the request list. It is unambiguous.
Cloud provider credentials. /.aws/credentials, /.aws/config, /service-account.json, /.boto, /.s3cfg, /.docker/config.json. Long-lived keys that grant API access to whatever the victim runs.
Environment files, exhaustively. /.env and every variant a careless deploy might leave behind: .env.local, .env.production, .env.backup, .env.bak, .env.old, .env.example, and the same file hunted under backend/, admin/, config/, and api/. Thirteen shots at the single most reliable secret-bearing file on the modern web.
Version control. /.git/HEAD, /.git/config, /.git-credentials, /.gitconfig. An exposed .git directory is a full source-code disclosure, and .git-credentials stores repository passwords in plaintext by design.
CI/CD. /.github/workflows/deploy.yml, /.gitlab-ci.yml. Pipeline definitions name the secrets they consume, the registries they push to, and frequently the infrastructure they touch.
Remote access. /.ssh/id_rsa. The private key itself.
Sync and storage. /rclone.conf โ the single most-requested path in the entire campaign, 20 hits. An rclone config is a bundle of live tokens for whatever cloud storage the victim has mounted: Drive, S3, Dropbox, B2, all of it, already authenticated.
And one that dates the operation precisely:
/.codex/config.toml โ 16 requests.
That is the configuration file for an AI coding agent. It sits alongside the AWS keys and the SSH key in the same sweep, hunted with the same priority. Whoever maintains this target list has added developer AI tooling to the same tier as cloud credentials, because a coding agent's config is now a credential-bearing file like any other. Six months ago that path would not have been on anybody's list. It is on this one, sixteen times.
The list, read whole, is not a vulnerability scan. It is a secrets scan. It does not probe for injectable parameters, unpatched CMS versions, or exploitable endpoints. It asks, two hundred and thirty-three times, in every dialect it knows: is there a key lying on the floor here?
Real crawlers and this thing, side by side
The cleanest discriminator is not the user-agent. It is what happens after the request.
Apple's genuine Applebot arrives from 17.22.x, 17.246.x, and 17.241.x โ inside 17.0.0.0/8, which Apple has owned outright since the 1990s. Across 5,611 requests it produced a 404-rate of 0.0%. Every single thing it asked for existed. That is what a crawler looks like: it reads your sitemap, follows your links, and requests pages you actually published.
The impostor claiming Applebot arrives from 35.245.x, rented Google Cloud. 227 requests, 209 distinct paths, 404-rate 100.0%. Not one thing it asked for existed, because it was never reading the site โ it was reciting a list.
The pattern repeats across every identity in the wardrobe:
| identity | genuine source | 404-rate | impostor source | 404-rate |
|---|---|---|---|---|
| Applebot | 17.22 / 17.246 / 17.241 (Apple) | 0.0% | 35.245.x (GCP) | 100.0% |
| GPTBot | 74.7.x | 5.6% | 34.140.x / 34.125.x (GCP) | 74โ75% |
| ClaudeBot | 216.73.x | 0.0% | 34.140.x (GCP) | 74.6% |
Real GPTBot fetched 4,601 distinct paths and missed on 5.6% of them โ normal drift for a large crawler working from a stale index. The fake fetched 42 paths and missed on three quarters of them.
You do not need to verify a crawler's identity cryptographically to catch this. You need to notice that one of them reads your site and the other one interrogates it. A crawler that never gets a 200 is not a crawler.
The company it imagined, and the one it found
Seventeen hostnames were tried. Twelve of them are inventions:
clients. 285 backoffice. 156
qa. 221 login. 121
crm. 218 test. 112
my. 202 api. 62
customer. 192 oauth. 9
app. 3
None of those exist here and none ever have. They are a portrait of a business drawn from priors โ a company with customers, a sales system, a staging environment, and a back office. The tool is not enumerating DNS; it is enumerating an assumption about what an organisation looks like, and then asking each imagined department for its credentials.
Five of the seventeen are not inventions.
kiwix. 123
library1. 104
comfy-ui. 73
kiwix is an offline-Wikipedia mirror. comfy-ui is a local image-generation front end. library1 is a document store. These are not names anybody guesses. They are not in a subdomain wordlist, they are not business-shaped, and they would not occur to somebody modelling a company โ they are specific, idiosyncratic services that happen to run on this particular network.
Which means part of this target list was not guessed at all. It was collected. Certificate transparency logs are the obvious source โ every publicly issued certificate publishes its hostnames into a searchable global feed, and a homelab that terminates TLS properly writes its own inventory into that feed as a side effect of doing the right thing.
So the fleet arrives carrying two lists laid end to end: a generic one it brings everywhere, and a specific one it built about this target beforehand. The generic list is noise. The specific list is reconnaissance, performed at some earlier date by something that was not this scanner, and handed to it.
That is the part worth flinching at. The costume changes are loud and clumsy. The homework was done quietly.
It is loudest right now
The campaign's shape over three weeks:
| date | requests | addresses |
|---|---|---|
| 2026-07-19 | 5 | 1 |
| 2026-07-21 | 42 | 3 |
| 2026-07-23 | 3 | 1 |
| 2026-07-27 | 26 | 1 |
| 2026-07-29 | 612 | 3 |
| 2026-07-30 | 121 | 1 |
| 2026-08-05 | 150 | 1 |
| 2026-08-07 | 838 | 7 |
| 2026-08-08 | 521 | 5 |
July is reconnaissance-grade: single addresses, a handful of requests, days apart. Something changes on the 29th โ 612 requests across three addresses in one day, a twenty-fold jump. Then a lull, then the last forty-eight hours: 838 requests from seven addresses, and 521 more from five before the day was half over.
The figure for 2026-08-08 is not a total. It is a partial count of a day still in progress at the time of writing.
Whatever this is, it is not winding down and it is not a historical artefact. The escalation is happening now, the address pool is widening rather than rotating, and every burst is still terminating in a layer-3 ban within seconds of starting.
Steelmanning the boring explanation
The comfortable reading is that this is commodity noise โ an off-the-shelf secrets scanner with a user-agent randomiser bolted on, sprayed indiscriminately at every address on the internet, and there is no operator here worth the name.
Much of that survives contact with the evidence, and it should be conceded. The path list is commodity: .env, .git, .aws/credentials and rclone.conf appear in every open-source secrets scanner published in the last three years. The infrastructure is rented and disposable. The bursts are unthrottled to the point of self-defeat โ 37 requests per second from a single address will trip any rate limiter built this decade, and it tripped ours within seconds. Nothing here is careful.
Three things resist the reading.
The identity list is curated, and it is current. The wardrobe contains Claude-SearchBot, Claude-User, OAI-SearchBot, MistralAI-User, Amzn-SearchBot and Applebot-Extended โ tokens that did not exist eighteen months ago, several of which were published within the last year. Somebody has been maintaining this list against vendor documentation as it changes. A fire-and-forget commodity tool does not track the AI-crawler namespace.
The target list is current too. /.codex/config.toml is not in any scanner wordlist old enough to be called commodity. Its presence, at the same request weight as .aws/credentials, means the list is being edited by someone who is following what developers actually install.
The host enumeration is imaginative rather than mechanical. clients., qa., crm. are not from a subdomain-brute wordlist ordered by frequency โ they are guesses about what a business would run. The tool is modelling the victim, not just the DNS.
None of that makes this sophisticated. It makes it maintained. Those are different properties, and only the second one tells you somebody is still at the keyboard.
The negative that matters most
The query that opened this investigation was a filter on our own web-threats API:
/api/web-threats/ips/?agent_product=Applebot-Extended&sort=requests
It returned 17 addresses. The fleet is 23.
Six addresses are missing, and they are missing for an instructive reason. Our classifier assigns each address a single agent_product โ its dominant identity. An address that spent most of its burst wearing ClaudeBot and only touched Applebot-Extended a dozen times is filed under Claude. It is in the same fleet, running the same paths, from the same rented range, in the same five-second burst shape. It simply does not answer to the name we searched for.
This is worth sitting with, because the failure is not a bug. Every field in that response is accurate. agent_product faithfully records the most common identity the address presented. The problem is that we asked the index a question about a disguise, and it answered about the disguise, precisely and correctly, and in doing so hid a quarter of the fleet.
The address that made this campaign legible โ 34.17.101.240, the one wearing 56 identities in five seconds โ is not in the 17. Filtering by any single mask would never have found the wardrobe. It was found by inverting the query: take the addresses, discard the label, and ask what else they wore.
There is a rule in this for anyone running a classifier over adversary-controlled input. A field populated from attacker-supplied data is a record of what the attacker said, not of what they are. agent_product is downstream of the user-agent header. The user-agent header is written by the adversary. Grouping by it produces clean, correct, well-normalised statistics about a fiction โ and the more disciplined your normalisation, the more convincingly the fiction holds together.
The same trap in a different register appeared elsewhere in this platform's own data recently: 47 addresses sending browser user-agents were briefly read as human visitors, until the addresses turned out to all sit in one /24 belonging to Meta. Identity by user-agent is not weak evidence. It is not evidence.
What to take from it
Three artefacts were offered as identity here, and all three were false:
- the user-agent โ a token that cannot make requests, in a format Apple does not use
- the
agent_productclassification โ accurate, derived, and describing a costume - the hosting โ Google Cloud, which confers no more legitimacy than a rented room confers respectability
What survived was behaviour. It never asked for robots.txt. It never received a 200. It asked 233 questions and all of them were where do you keep your keys. It wore 56 faces in five seconds, and nine times it forgot to put one on.
The operational conclusion is narrow and cheap to apply: verify crawlers by network, never by name. Apple publishes its ranges. Google, OpenAI and Anthropic publish theirs. A request claiming any of those identities from rented cloud egress is answerable in one step, with no reputation system and no threat feed. Our own ban logic already encodes this โ the verified-crawler allowlist is keyed on autonomous system number and voided entirely for intents a real crawler never has, credential_harvest among them. That is why this fleet was banned on arrival, at layer 7 and again at layer 3, while genuine Applebot has been reading these dossiers from 17.0.0.0/8 untouched for months.
But the thing worth keeping is smaller and stranger than the defence.
Somebody sat down and built a tool to impersonate every AI company's crawler in sequence, to find out which of those names would open a door. They kept the list current as the vendors published new tokens. They added AI coding-agent configs to their target list within months of those tools shipping. They understood the AI-crawler ecosystem well enough to enumerate all of it.
And they did not understand it well enough to know that one of the names they were wearing was never a crawler at all โ that Applebot-Extended is a word you write on your own door to be left alone, and that by putting it on and knocking, they had announced themselves as something that had read the documentation without understanding a line of it.
Series: The Census. Preceded by TI-2026-102A โ "The Census: A Scanner That Breaks In, Asks What You Are, and Leaves."
All figures drawn from the web-threats request corpus, 2026-07-19 to 2026-08-08. Source addresses are recorded in the campaign record and are, at time of writing, active on both the CrowdSec decision list and the MikroTik layer-3 blocklist.