Security

Credential Scanners Are Wearing Crawler Costumes

Credential Scanners Are Wearing Crawler Costumes
71.9 percent of traffic claiming to be Googlebot was hunting for .env files. Of 13,670 credential probes wearing a named crawler user agent, zero came from a verified vendor IP.

Between March and August, 12,134 requests to this site announced themselves as Googlebot. 8,725 of them — 71.9% — were hunting for credentials. /.env, /.git/config, /.aws/credentials, deploy workflows, database dumps.

Google was not doing this. Not one of those requests came from an address Google publishes.

Somewhere along the way, “Googlebot” stopped being an identity and became a costume — and the same is now true of GPTBot, ClaudeBot, PerplexityBot and the rest. The reason is straightforward, and it is our fault rather than theirs: we told everyone these names get waved through.

The number that settles it

Across five months and 1,265,337 requests, 171,555 were credential probes. Most wore no disguise at all — ordinary scanner traffic, the background radiation of running anything on the public internet.

But 13,670 of those probes arrived wearing the user agent of a named AI or search crawler. So I checked every one against the vendor’s published IP ranges.

Zero came from a verified vendor address.

Not “a small number.” Zero. My first pass reported seven, and all seven turned out to be genuine Googlebot crawling spam search-result URLs on my own site that happened to contain the word “dumps” — my pattern matching, not Google’s behaviour. Once those came out, the count was clean.

That is the whole finding, and it is worth stating plainly because it makes the mitigation obvious: no legitimate AI crawler is probing your site for secrets. Every request that appears to be one is an impostor, and a single check separates them.

Which costumes get worn

The share of each identity’s traffic that was credential probing:

Claimed identity Probes Share of its traffic
Googlebot 8,725 71.9%
Google-Extended 120 69.0%
Perplexity-User 51 56.7%
meta-externalagent 3,057 39.3%
CommonCrawl CCBot 69 24.6%
PerplexityBot 176 20.2%
OAI-SearchBot 185 15.9%
ChatGPT-User 359 10.2%
ClaudeBot 157 4.1%
Amazonbot 314 4.3%
GPTBot 174 1.9%
Bytespider 41 0.5%
bingbot 55 0.4%

Read that as a ranking of which names are worth stealing. Googlebot tops it by a distance, because Googlebot is the identity every webmaster has been trained for twenty years not to block. Google-Extended — the opt-out control for AI training — sits second, which is a bleak little detail: the user agent that exists so publishers can decline is itself being impersonated to break in.

At the bottom sit GPTBot, Bytespider and bingbot at under 2%. Those are crawlers behaving like crawlers.

What they were after

The top targets, in order:

224  /.git/config
221  /.env.production
215  /.env.local
211  /.env
204  /.gitlab-ci.yml
191  /.github/workflows/deploy.yml
146  /.aliyun/credentials
133  /.tencent/credentials
129  /.qcloud/credentials
128  /.aliyun/config.json
127  /.env.staging
 97  /.env.bak
 95  /.env.backup
 87  /secrets.json
 74  /.aws/credentials
 72  /credentials.json

The .env family and exposed .git directories are the classics — a committed .env or a web-served .git hands over database passwords and API keys in one request.

The CI files are the more modern touch. /.gitlab-ci.yml and /.github/workflows/deploy.yml rarely contain secrets directly, but they name your deployment targets, your registries, and the secret names your pipeline injects. That is reconnaissance rather than a smash-and-grab.

And note the Alibaba, Tencent and Qcloud credential paths sitting alongside AWS. Those appear in fixed proportion with everything else, which suggests a single toolkit with a broad wordlist rather than a targeted operation. Nobody looked at this site and decided it probably ran on Qcloud.

Where it actually comes from

Here is the detail I keep returning to. The three busiest sources of probes wearing Meta’s crawler identity:

1,047  34.63.249.85    →  85.249.63.34.bc.googleusercontent.com
  715  34.91.139.167   →  167.139.91.34.bc.googleusercontent.com
  667  34.14.90.240    →  240.90.14.34.bc.googleusercontent.com

Meta’s crawler, running on Google Cloud. The requests wearing OpenAI’s ChatGPT-User came from the same place. This is rented compute with a forged label, and the reverse DNS gives it away in one lookup.

The single busiest impostor — 6,092 probes as Googlebot from 101.47.155.116 — has no reverse DNS at all. Real Googlebot resolves to crawl-*.googlebot.com. Every time. The absence of a PTR record is not proof of much on its own, but combined with “claims to be Googlebot” it is conclusive.

It is not even a disguise. It is a rotation.

That address is worth a closer look, because it reframes the whole thing. Its user agents across the campaign:

3,261  curl/7.68.0
3,220  python-requests/2.28.0
3,138  Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...
3,125  Mozilla/5.0 (X11; Linux x86_64) ... Chrome/120
3,111  Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) ...
3,072  Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)

Six identities, near-identical counts. Googlebot is not a targeted disguise — it is entry six on a list, cycled per request. Here are six consecutive requests from that address:

01:40:54  /.git/config      Googlebot/2.1
01:40:55  /oss.yaml         Firefox 121 on Windows
01:40:56  /.env.            Chrome on Windows
01:40:56  /credentials.js   python-requests/2.28.0
01:40:57  /.env.local%00    (no user agent)
01:40:57  /.env.local~      Chrome on Windows

Five identities in three seconds, from one address.

If your rules challenge curl, wave through Googlebot, and rate-limit by user agent, this scanner is simultaneously blocked, allowed, and counted as five separate clients — while remaining one machine working through a wordlist. Any control keyed to the user agent is not making a decision about the client. It is making a decision about a string the client rewrites between requests.

This is recent, and it arrived all at once

Probes wearing a crawler costume, by month:

Month Probes
March 23
April 45
May 85
June 191
July 9,396
August (to the 10th) 3,930

A forty-nine-fold jump in a month. And it was not gradual: 6,451 of July’s probes landed on a single day, 24 July. One campaign, one afternoon, and then a sustained background level roughly twenty times higher than before it.

Whatever tooling did this picked up the technique between June and July. If you have not looked at your own logs since the spring, you are looking at a different threat than the one you remember.

Why this breaks the standard advice

The conventional guidance for bot management is to allowlist the good crawlers so you do not accidentally block search traffic, then rate-limit or challenge the rest. Practically every WAF ships a “verified bots” or “known good bots” toggle built on this idea.

The trouble is that a large amount of that tooling matches on the user agent string, which is a value the client chooses. If your allowlist rule is “user agent contains Googlebot, skip the challenge,” you have not built an allowlist. You have published a password, and it is in every scanner’s default configuration.

This is the same structural mistake that shows up inside agent systems: trusting a self-reported identity because it is inconvenient to verify. In agent design it is the confused deputy — a component with real authority acting on instructions from something that merely claimed the right to give them. Here the deputy is your firewall.

What to do about it

Verify, or do not allowlist. There is no middle option that means anything. Verification is not hard — three of the major vendors publish their ranges as JSON you can fetch and cache:

openai.com/gptbot.json · chatgpt-user.json · searchbot.json
perplexity.ai/perplexitybot.json · perplexity-user.json
developers.google.com/search/apis/ipranges/*.json

For the rest, use reverse DNS and then forward-resolve the hostname back to the same address. A PTR record alone can be set by whoever controls the IP; the round trip is what makes it evidence. Amazon uses .crawl.amazonbot.amazon, Apple .applebot.apple.com, Microsoft .search.msn.com.

Alert on the combination, not the paths. Credential probing is constant and mostly noise. “Credential probe plus a crawler user agent” is a much rarer signal, and on this evidence it is never legitimate. That makes it a clean, low-volume alert with essentially no false positives — the best kind.

Check what an allowlisted identity can actually reach. If skipping the bot challenge also skips rate limiting, one forged header buys an attacker unlimited attempts. Whatever the allowlist grants is what impersonating it is worth.

Then fix the boring thing. All of this only matters because a .env file might be sitting in a web root somewhere. The scanning is unavoidable; what it finds is not.

The general shape of it

A trusted identity that anybody can assert, checked by nobody, granted real privileges. We have seen this before with email senders, and it took SPF, DKIM and DMARC to make sender identity mean anything.

Crawler identity is at the pre-SPF stage right now. The verification mechanisms exist and some vendors publish what you need — but the checking is optional, so mostly it does not happen, and the gap between “claims to be Googlebot” and “is Googlebot” is where 13,670 requests went looking for my secrets.

Your logs already contain your version of this. It takes about a minute to find out.

Method, and why the user agent is unreliable in the first place: Crawled, Indexed, or Cited. On self-reported identity as an architectural problem inside agent systems: Threat Modeling an AI Agent.

Dominic Plouffe