113SEC  —  BLOG
X000Y000
[113SEC]TR
SUPPORT ↗
← ALL ARTICLES[ RESEARCH ] — Threat Research

It hunts AI keys too: six weeks of tracking a secret-file scanner running on Google Cloud

A tool operating from 43 Google Cloud IP addresses spent six weeks probing the web applications we protect for .env, Git and cloud keys, and for the settings and credential files of AI assistants. Here is what our logs showed, what worked and what did not, and how to test your own environment.

NO WAF LOGS18 SEP5 OCT23 OCT2 NOVTOOL A · 43 IPS · 83 SESSIONS

Executive summary

  • The tool and its infrastructure: Between 18 September and 2 November 2026, we tracked a tool (Tool A in this post) operating from 43 different IP addresses against the web applications we protect. All of them are Google Cloud addresses; 42 are in the Belgium region (europe-west1).
  • The list: Since 8 October every scan uses a fixed 112-path core list, plus a small "theme" that changes in most scans.
    • Next to .env, Git and AWS files, the core includes .claude/settings.json, .env.anthropic and .env.openai.
    • With the themes, the number of AI-assistant, LLM-platform and MCP paths grows to 32 (44 if other model providers such as AI21 and Mistral and voice/image APIs are counted). They include credential files such as .claude/credentials.json and .openai/credentials, and MCP configurations such as .cursor/mcp.json and .vscode/mcp.json.
  • Apparently centrally coordinated: the same theme arrived from different IPs on the same days (exactly the same set from 7 IPs between 30 October and 2 November), while the same IP brought a different theme on almost every visit (21 of the 22 repeat IPs).
  • Speed and returns: Half of the scans finish in under 2.5 seconds, so an IP block cannot stop the scan itself. But the addresses come back: 41 of 83 sessions came from an IP we had already seen.
  • What protected us: edge rules, which stopped 69% of the requests, and the fact that there was nothing to find in the web root. No data was exposed in any of the scans we reviewed.
  • The most important lesson: our SIEM saw only 11 of the tool's 42 addresses in October–November. We found the rest by searching WAF logs for the core list.
  • Not the only tool: in the same weeks we saw a second tool using five different browser identities, and in August a third tool that tried 17 AI-related paths. In 30 days, 205 different IPs looked for secret files on our systems; 54% of them were Google Cloud addresses.

Background

Harvesting keys from exposed files, and then monetizing them, is not new:

  • Unit 42 (2024): documented an extortion operation that used keys from exposed .env files on 110,000 domains to get into AWS environments, delete S3 data and leave ransom notes.
  • Sysdig, EMERALDWHALE (2024): reported an operation that scanned exposed .git/config files and collected more than 15,000 cloud credentials.
  • Sysdig, "LLMjacking" (2024): showed stolen cloud credentials being used to access AI models while the account owner paid the bill.

Our observations show this trend continuing. At least two separate scanning tools now look for the configuration and credential files of AI tools alongside cloud keys.

How we found Tool A

The first clue came from the SIEM. From late September, Wazuh raised alerts for scans of the same kind with its "many 4xx in a short time" rule. When we noticed a fixed 112-path list behind those alerts, we searched our WAF logs for the same list. In October–November, 11 addresses had produced alerts; 42 had tried the same list.

Period IPs Source
18 September 2 Alert lines and that day's traffic review note: 35.205.254[.]119 came with Tool A's truncated browser identity; 34.38.113[.]44 looked for .claude, .anthropic and gcloud files
24 September 1 34.62.82[.]165: a much larger list (283 requests according to the review note; a 4xx on 111 different paths, AI paths included)
26 September 1 34.38.113[.]44: a list very close to the October core (24 of the 30 paths that got a 4xx are in the core)
5 October – 2 November 42 WAF logs: 83 sessions over 20 days, 100 target scans

Both addresses in the 18 September row also scanned with the core list in October: 35.205.254[.]119 on 12 and 19 October, 34.38.113[.]44 on 10 and 20 October. We saw 34.62.82[.]165 only on 24 September.

How Tool A works

Fixed core, rotating theme.

  • All 93 target scans between 8 October and 2 November contained all 111 non-homepage core paths.
  • Most scans also added a mostly related set of 0–10 paths; most often 6.
  • Up to the afternoon of 23 October, the 66 scans that had a theme used 51 different sets. Apart from a session's second target receiving the same set, only 3 sets reappeared in another session.
  • From the afternoon of 23 October this changed: /.cursor/mcp.json and /.netrc, both first seen that day, were present in all 24 later scans. These trial paths seem to have joined the permanent list.
First seen Theme Example paths
5 October social media and accounting API keys, Vault, CI workflows /.env.facebook, /xero.json, /.vault.json, /.github/workflows/production.yml
8 October AI and media APIs, mobile app folders /.env.claude, /.env.elevenlabs, /.env.stability, /flutter/.env
9 October monitoring and email services /.env.betterstack, /.env.uptimerobot, /.env.brevo
10 October self-hosted CI, hosting platforms /.drone.yaml, /.woodpecker.yaml, /.netlify/.env, /.vercel/.env
11 October AI credentials; payments, authentication, email marketing /.claude/credentials.json, /.openai/credentials, /openai.key, /.env.mercadopago, /.env.frontegg
12 October AI and observability platforms /.env.ai21, /.env.writer, /grafana.json, /dynatrace.json
13 October AI assistants and LLM platforms, Azure /.claude/auth.json, /.anthropic/credentials.json, /.gemini/settings.json, /langsmith.json, /portkey.json, /.azure/credentials.json
16 October API key files, GitHub / GitLab tokens /.api-keys, /.github/token, /.gitlab/credentials
17 October Elasticsearch / Solr, business apps and content management systems /_cat/indices, /odoo.conf, /owncloud/config/config.php, /strapi/.env
18 October cloud development environments, Matrix, Zabbix /.config/gitpod/credentials, /.coder/credentials, /matrix/homeserver.yaml, /zabbix/conf/zabbix.conf.php
19 October hosting and fintech platforms /.env.kinsta, /.env.qovery, /.env.lithic
22 October MCP, gcloud /.continue/config.json, /mcp.json, /.config/gcloud/application_default_credentials.json
23 October MCP and credential files /.cursor/mcp.json, /.vscode/mcp.json, /.netrc, /.composer/auth.json
30 October server admin endpoints /actuator/env, /server-info, /.htaccess

Themes do not seem to match the target's technology. For example, the payment-provider theme was tried against an application that takes no payments, and the LLM-platform theme against an application that uses no AI.

The theme follows the task, not the IP.

  • 21 of the 22 IPs that came more than once brought different themes on different visits. For example, 34.53.184[.]30 tried 8 different themes in 10 target scans.
  • The same theme, meanwhile, came from different IPs:
    • Between 23 and 28 October, 6 IPs came with exactly the same three extra paths (/.cursor/mcp.json, /.netrc, /sentry.properties); 4 more IPs added a few paths to that trio.
    • Between 30 October and 2 November, 7 IPs came with exactly the same set of extras.

This pattern suggests worker machines receiving tasks or list updates from a shared controller.

Speed.

  • One IP's scan within an hour (a "session" in this post; 83 sessions) took a median of 2.5 seconds.
  • 29 sessions finished in under 2 seconds and 63 in under 10 seconds. The longest took 41 seconds.
  • 111–128 paths were tried per target.

Identity.

  • 97% of the requests came with this truncated browser identity (User-Agent) and an Accept-Encoding: gzip, deflate header: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
  • A real Chrome ends this string with (KHTML, like Gecko) Chrome/… Safari/….
  • The remaining requests used a full browser identity, most often a two-year-old Chrome 128. These were the home page and page resources.
  • In October–November, 53 addresses outside Google Cloud used the same truncated identity; none of them tried more than two thirds of the core. The identity alone is not a sufficient signature.

It reads the page first. The home page was requested in 83 of 100 target scans, and the JavaScript bundles the page loads in 22. The tool is most likely looking for keys left in those files.

Targets. Scans hit both the www and bare form of domain names, and also the raw IP addresses of our load balancers; 13 different names and addresses in total. We do not know how targets are selected.

What it looks for

Breakdown of the 112-path core:

Category Paths Examples
.env files 49 /.env, /.env.production, /api/.env, /laravel/.env
Application configuration 22 /appsettings.Production.json, /secrets.json, /config.php, /phpinfo.php
Cloud credentials 12 /.aws/credentials, /.boto, /.s3cfg, /aws-exports.js
Git 8 /.git/config, /.git-credentials, /.github/workflows/deploy.yml
Docker and deployment 7 /docker-compose.yml, /.docker/config.json, /Dockerfile
Backups and databases 6 /database.sql, /.env.bak, /wp-config.php.bak
AI tools 4 /.claude/settings.json, /.claude/settings.local.json, /.env.anthropic, /.env.openai
Other 4 home page, /phpinfo, /_profiler/phpinfo, /wp-content/debug.log

The list of paths Tool A tried between 5 October and 2 November is attached to this post: after removing 21 paths specific to our sites, 411 paths remain; 32 belong to AI assistants, LLM platforms and MCP (44 with other AI APIs).

Why AI files

Coding assistants and LLM platforms keep their settings, and sometimes their keys, in plain-text files inside the project folder:

  • .claude/settings.json, .gemini/settings.json: assistant settings, allowed commands, sometimes environment variables.
  • .claude/credentials.json, .anthropic/credentials.json, .openai/credentials, openai.key: as the names suggest, files meant to hold keys or session credentials directly.
  • .cursor/mcp.json, .vscode/mcp.json, mcp.json, .continue/config.json: MCP server definitions. These files can contain tokens for databases, GitHub or cloud accounts; a single file can carry keys to several systems at once.
  • .env.anthropic, .env.openai, langsmith.json, portkey.json, helicone.json: keys for model providers and LLM observability platforms.

If these files are copied to the web root along with the project folder by mistake, a single GET request can hand someone else the key to your AI account (leaving you with the bill) and the tokens to systems connected through MCP. AI paths sitting in the fixed core, and new ones being tried regularly, show that attackers consider these files worth looking for. Whether they actually find them, we do not know.

A second tool in the same weeks

Between 22 September and 7 October, another tool, also from Google Cloud, hit one of the applications Tool A also scanned (Tool B):

  • 12 IPs, all in the US East (us-east1) and Taiwan (asia-east1) regions. It never came from the Belgium region Tool A used.
  • Five full browser identities: Chrome 131, Edge 130, Firefox 133, Safari/macOS and Safari/iPhone. The same target received requests with these five identities within a few seconds, and most paths were requested two or three times.
  • The same list within each period:
    • In each period, the IPs' lists of paths that got a 4xx are exactly the same: 18 paths on 22–24 September, 44 on 25–29 September, 74 on 2–7 October.
    • At least part of the difference between periods comes from our side. On 24 September we started returning 404 for paths with extensions too; none of the 26 paths added after 25 September starts with a dot.
    • The two IPs whose full list we saw in October's WAF logs tried 132 different paths. That list contained only 36 of the 112 paths in Tool A's core (32%).
  • No AI paths. Its list is mostly developer and server files such as .bash_history, .pgpass, .pypirc, .vault-token and .kube/config.

We do not know whether the two tools belong to the same person or group.

These are not the only tools

The two tools are part of a much broader scanning picture in our logs:

  • Between 3 October and 2 November, 205 different IPs tried at least 10 different sensitive paths on our systems. 110 of them (54%) were Google Cloud addresses; 42 were Tool A. 92% of these IPs' requests to the paths in the query below were stopped at the WAF.
  • In August, a third tool, also from Google Cloud addresses, did the same job with path-encoding tricks (/%2eenv, ..%252F..%252F.env, /proc/self/environ):
    • It presented itself as more than 20 different bots, including ChatGPT-User, GPTBot, OAI-SearchBot, ClaudeBot, Claude-User, PerplexityBot, Amazonbot and Meta's crawler.
    • It even used the Google-Extended identity, which is only a robots.txt token; no real request ever arrives with it.
    • Each of its five IPs tried the same 17 AI paths: /.cursor/mcp.json, /.mcp.json, /.claude.json, /claude_desktop_config.json, /.codex/config.toml, /.aider.conf.yml, /.continue/config.json and others.
  • Another tool on 2 November changed identity on almost every request (158 different identities in 187 requests). Some of them imitated internet scanning services such as Censys, LeakIX, ZoomEye, Shodan and Palo Alto Expanse.

Lesson: real bots do not try hundreds of secret-file paths in a row. Before trusting a bot identity, verify it against the provider's published IP lists or with reverse DNS.

What worked for us, and what did not

What worked:

  1. Edge rules.
    • Tool A's 42 IPs sent 11,962 requests in October–November; 8,274 of them (69%) were stopped at the WAF.
    • 4,191 of the blocked requests were stopped by our own rule for path segments that start with a dot and known secret-file paths.
  2. Nothing to find.
    • Requests that passed the WAF received a 404 or the empty application shell.
    • During incident reviews, we confirmed by size and MD5 hash that the 200 responses were the home page.
    • We also scanned our JavaScript bundles for keys separately.

What did not work, or came too late:

  1. SIEM visibility.
    • Only 12 of the 83 sessions in October–November produced a Wazuh alert. 31 of the tool's 42 addresses never reached the SIEM in October–November.
    • These sessions either hit targets whose web logs we were not feeding into the SIEM at the time, or never reached the server because the WAF stopped every request.
    • Lesson: feed your WAF logs into the SIEM too.
  2. IP blocking.
    • Half of the scans are over in less than 2.5 seconds; however fast the alert, decision and block chain is, that scan is usually done already.
    • Behind an application-layer load balancer such as ALB, a local firewall rule on the server does not stop the traffic: packets come from the load balancer, not from the client. If your server does not log the real client IP, it is worse: the automated block can block the load balancer's own address and cause an outage.
    • A block does not stop that scan, but it can stop the returns: 41 of the 83 sessions came from an IP we had already seen.
    • Assuming we had blocked each IP after its scan:
      • A 30-minute block (the duration of our server rule) would have stopped none of them.
      • A 24-hour block would have cut 16.
      • A 7-day WAF IP list would have cut 31.
    • Had we tied the block to SIEM alerts only, a 7-day list would have cut 7 sessions, not 31. Trigger the block from WAF logs.
  3. IP reputation lists, partly. AWS's managed IP reputation list recognized only 16 of the tool's 42 addresses (26% of blocked requests). Freshly rented cloud IPs can reach these lists late.
  4. "200 for everything" (the SPA trap).
    • In mid-September we returned the home page with status 200 for every unknown path.
    • In that state the scanning tool may think it "found" something, and the SIEM raises no alert because it never sees an error.
    • Once we started returning 404 for paths with extensions and paths starting with a dot, the scans became visible in the SIEM.

What you can do in your own environment

A 30-second self-test

SITE=https://yoursite.com
for path in / /this-path-does-not-exist /.env /.git/config /.aws/credentials /.claude/settings.json /.cursor/mcp.json /docker-compose.yml /Dockerfile; do
  printf '%-28s %s\n' "$path" "$(curl -s -o /dev/null -w '%{http_code} %{size_download} bytes' "$SITE$path")"
done
  • The first two lines are baselines: the home page and a path that does not exist. All sensitive paths should return 404 (or 403). If you get a 200, compare the size with the first two lines: if it is the same, you are in the SPA trap; if it differs, the file may really be public, so remove it immediately and rotate the keys inside.
  • Run the test against your server's raw IP address (with curl -k) and the default virtual host as well. Tool A scanned those too.

nginx

We tested the configuration below on nginx 1.27.5. All 24 sensitive paths we tried returned 404. The application's own pages and API were not affected.

server {
    # ...

    # ACME challenge: 404 if the file does not exist (never fall through to the SPA)
    location ^~ /.well-known/acme-challenge/ { try_files $uri =404; }

    # API: the ^~ modifier stops the regex rules below from being tried, so sensitive paths are blocked here separately
    location ^~ /api/ {
        location ~ /\. { return 404; }
        location ~* "^/api/(.*/)?(actuator|_profiler|phpinfo|server-info|server-status)" { return 404; }
        proxy_pass http://app;
    }

    # Any path with a segment that starts with a dot returns 404 (only /.well-known/ is allowed)
    location ~ /\.(?!well-known/) { return 404; }

    # Known sensitive names without an extension, and admin endpoints
    location ~* "^/(dockerfile|jenkinsfile|procfile|makefile|credentials|env|id_rsa|id_ed25519|kubeconfig|phpinfo|server-info|server-status|actuator|_profiler|_cat|_cluster|_nodes|_users|_config|solr)(/|$)" { return 404; }

    # Paths with an extension: served if the file really exists, otherwise 404 (never index.html)
    location ~* "\.[a-z0-9]{1,8}$" { try_files $uri =404; }

    # Everything else goes to the SPA
    location / { try_files $uri /index.html; }
}

Before you use it:

  • This configuration is for an SPA whose files are served from disk. If the whole application is proxied to a backend (Next.js, .NET, Node), the extension rule can break static files; in that case use only the dot and sensitive-name rules.
  • SPA routes that contain a dot (/users/john.doe, /docs/v1.2) will also get a 404.
  • If your load balancer's health check uses an endpoint these rules block, such as /actuator/health, open it first with location = /actuator/health { … }; an exact match wins over regular expressions. The same applies to monitoring endpoints such as server-status.
  • Put regular expressions that contain curly braces or semicolons in quotes; otherwise nginx will not load the configuration (nginx -t fails).

AWS WAF

A rule that catches, at the edge, requests with a path segment starting with a dot (except /.well-known/). It passed AWS's capacity check (32 WCU).

{
  "Name": "secret-file-paths",
  "Priority": 10,
  "Statement": { "AndStatement": { "Statements": [
    { "ByteMatchStatement": { "SearchString": "/.", "FieldToMatch": { "UriPath": {} },
        "TextTransformations": [ { "Priority": 0, "Type": "URL_DECODE" }, { "Priority": 1, "Type": "NORMALIZE_PATH" } ],
        "PositionalConstraint": "CONTAINS" } },
    { "NotStatement": { "Statement": { "ByteMatchStatement": { "SearchString": "/.well-known/", "FieldToMatch": { "UriPath": {} },
        "TextTransformations": [ { "Priority": 0, "Type": "URL_DECODE" }, { "Priority": 1, "Type": "NORMALIZE_PATH" } ],
        "PositionalConstraint": "STARTS_WITH" } } } }
  ] } },
  "Action": { "Count": {} },
  "VisibilityConfig": { "SampledRequestsEnabled": true, "CloudWatchMetricsEnabled": true, "MetricName": "secret-file-paths" }
}
  • Run it in Count mode for a few days first; switch it to Block once you are sure it does not catch a legitimate path.
  • update-web-acl replaces the whole rule list. Fetch the current rules with get-web-acl first, add this rule, and send it with the LockToken. Make sure the Priority value does not clash with an existing rule.
  • SearchString is a binary (blob) field; to pass plain text to AWS CLI v2, add the --cli-binary-format raw-in-base64-out parameter.

Search your WAF logs for Tool A (CloudWatch Logs Insights)

This query uses 10 paths from the core list that are rare in other scanning tools. Over a month of traffic in our own logs, it returned only Tool A's 42 IPs: each of them tried all 10 paths, and no other IP reached 6.

fields httpRequest.clientIp as ip, httpRequest.uri as uri
| filter uri in ["/.claude/settings.local.json", "/.env.anthropic", "/.env.openai", "/.amplifyrc", "/aws-exports.js", "/sendgrid.env", "/runtime-config.js", "/env-config.js", "/.docker/laravel/app/.env", "/config/aws.yml"]
| stats count_distinct(uri) as signature_paths, count(*) as requests, min(@timestamp) as first_seen, max(@timestamp) as last_seen by ip
| filter signature_paths >= 6
| sort first_seen asc

A second query to see secret-file scanning in general. In our logs it returned 205 IPs: Tool A's 42, the 2 Tool B IPs with WAF records, and 161 other addresses.

fields @timestamp, httpRequest.clientIp as ip, httpRequest.uri as uri, action
| filter uri like /(?i)\/(\.|%2e)env|\/(\.|%2e)git\/config|\/(\.|%2e)aws\/credentials|\/(\.|%2e)claude\/settings|\/(\.|%2e)cursor\/mcp\.json/
| stats count_distinct(uri) as distinct_paths, count(*) as requests, sum(action = "BLOCK") as blocked, min(@timestamp) as first_seen, max(@timestamp) as last_seen by ip
| filter distinct_paths >= 10
| sort requests desc
  • If blocked is lower than requests, some of that IP's requests reached your application; check your application logs for the responses returned to those requests.
  • The queries work if your WAF logs go to CloudWatch Logs; if they go to S3, build the same logic in Athena. If a logging filter limits WAF logs to blocked requests only, the counts will be incomplete.

Wazuh

  • Wazuh's built-in rule 31151 (many 4xx from the same source in a short time; frequency="14" within 90 seconds in the rule definition) catches these scans, but only if your web server really returns 404 for unknown paths (see the SPA trap).
  • Write blocks to a WAF IP set shared by all your web ACLs and keep them for at least a few days. Trigger them from WAF logs, because not every scan reaches the SIEM.

On the development side

  1. Put only the build output (dist/, public/) in the web root, never the project folder. Keep .claude/, .cursor/, .vscode/, .gemini/, .git/ and .env* out of your deployment package entirely.
  2. Add .env* files and personal settings files such as .claude/settings.local.json to .gitignore. A shared .claude/settings.json can stay in the repository, but it must not contain keys.
  3. Keep keys in a secrets manager, not in configuration files. In MCP configurations, pass tokens through environment variables instead of writing them into the file.
  4. Scan front-end bundles for keys during the build.
  5. If you suspect a key was exposed, rotate the key first, then look at the logs.

Indicators

Cloud IPs change hands often. Instead of blocking these addresses permanently, we recommend turning the behavioral signature below into a detection rule that runs on WAF or edge logs.

Tool A's behavioral signature:

  • More than 110 different secret-file paths from the same source in less than a minute, including the 10 paths of the signature query above.
  • The truncated identity Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 with Accept-Encoding: gzip, deflate on secret-file requests. Other tools use this identity too, so it is not sufficient on its own.

Tool A's IP addresses (43 addresses; defanged with [.]; dates in UTC; regions verified against Google's published IP ranges on 2 November 2026):

IP                  first seen   last seen    region
35.205.254[.]119    2026-09-18   2026-10-19   europe-west1
34.38.113[.]44      2026-09-18   2026-10-20   europe-west1
34.62.82[.]165      2026-09-24   2026-09-24   europe-west1
34.52.229[.]253     2026-10-05   2026-10-05   europe-west1
34.77.137[.]207     2026-10-05   2026-10-05   europe-west1
34.78.213[.]249     2026-10-05   2026-10-05   europe-west1
34.140.234[.]80     2026-10-05   2026-10-08   europe-west1
35.189.229[.]134    2026-10-05   2026-10-13   europe-west1
34.140.132[.]132    2026-10-08   2026-10-11   europe-west1
34.79.51[.]100      2026-10-08   2026-10-22   europe-west1
34.52.133[.]111     2026-10-09   2026-10-09   europe-west1
35.205.88[.]64      2026-10-09   2026-10-24   europe-west1
34.156.249[.]47     2026-10-10   2026-10-10   europe-west1
34.52.150[.]124     2026-10-10   2026-10-10   europe-west1
34.62.189[.]80      2026-10-10   2026-10-20   europe-west1
35.241.178[.]74     2026-10-11   2026-10-11   europe-west1
207.175.194[.]102   2026-10-11   2026-10-18   europe-west1
207.175.220[.]59    2026-10-11   2026-10-24   europe-west1
34.53.184[.]30      2026-10-12   2026-10-30   europe-west1
34.79.12[.]228      2026-10-13   2026-10-23   europe-west1
35.240.100[.]200    2026-10-13   2026-10-24   europe-west1
34.156.22[.]151     2026-10-16   2026-10-17   europe-west1
34.62.250[.]35      2026-10-16   2026-10-17   europe-west1
136.65.74[.]74      2026-10-17   2026-10-17   us-central1
34.14.99[.]143      2026-10-17   2026-10-24   europe-west1
35.241.239[.]86     2026-10-18   2026-10-23   europe-west1
34.62.25[.]61       2026-10-18   2026-10-27   europe-west1
34.22.126[.]178     2026-10-19   2026-10-19   europe-west1
34.34.160[.]116     2026-10-19   2026-10-19   europe-west1
34.53.212[.]113     2026-10-19   2026-10-27   europe-west1
35.241.202[.]92     2026-10-20   2026-10-20   europe-west1
34.79.59[.]9        2026-10-20   2026-10-22   europe-west1
207.175.98[.]108    2026-10-22   2026-10-22   europe-west1
34.62.18[.]205      2026-10-22   2026-10-22   europe-west1
34.53.227[.]51      2026-10-22   2026-10-23   europe-west1
34.140.179[.]23     2026-10-23   2026-10-23   europe-west1
104.199.36[.]53     2026-10-28   2026-10-28   europe-west1
34.62.116[.]145     2026-10-30   2026-10-30   europe-west1
34.52.234[.]95      2026-11-01   2026-11-01   europe-west1
34.77.241[.]117     2026-11-01   2026-11-01   europe-west1
23.251.135[.]142    2026-11-02   2026-11-02   europe-west1
34.62.246[.]24      2026-11-02   2026-11-02   europe-west1
34.76.27[.]30       2026-11-02   2026-11-02   europe-west1

Tool B's IP addresses (22 September – 7 October; all from SIEM records, two also with WAF records):

Period IPs Regions
22–24 September 34.75.172[.]231 · 34.80.89[.]28 · 104.155.231[.]26 us-east1, asia-east1
25–29 September 35.231.170[.]94 · 34.81.122[.]163 · 34.138.59[.]13 · 34.23.9[.]107 · 35.201.136[.]76 us-east1, asia-east1
2–7 October 35.190.129[.]55 · 34.80.69[.]73 · 34.138.20[.]242 · 136.108.44[.]83 us-east1, asia-east1

Downloads:

MITRE ATT&CK mapping

Technique Observation
T1595.003 — Active Scanning: Wordlist Scanning secret-file scanning with a fixed core plus a rotating theme
T1594 — Search Victim-Owned Websites downloading the home page and JavaScript bundles
T1589.001 — Gather Victim Identity Information: Credentials attempt to collect exposed credentials
T1552.001 — Unsecured Credentials: Credentials In Files the targeted files; had a file been found, this is the technique that would follow

Every scan we observed stayed at the attempt stage. Wazuh tags rule 31151 as T1595.002.

Method, data sources and limitations

Definitions:

  • Session: one IP's requests within one hour.
  • Target scan: the part of a session that goes to a single name or address. The 83 sessions give 100 target scans.

Data sources:

  • AWS WAF logs, 3 October – 2 November 2026 (up to 22:40 UTC). For one of the applications, also 4 August – 3 September.
  • Wazuh alerts in our SOC data lake, August – November. These alerts include the raw log lines (path and browser identity) of requests that received a 4xx.
  • Our incident review notes.
  • We have no WAF logs for 3 September – 3 October. September details come from alert lines and review notes.

How we assigned an IP to a tool:

  • Tool A:
    • In the period with WAF logs, trying at least 105 of the 111 non-homepage core paths. Of the 95 addresses using the truncated identity, 42 met this; the next one tried only 70.
    • In September (no WAF logs), using only the truncated identity in alert lines and largely containing the October core paths that reach the server: 83% for 34.38.113[.]44's 26 September scan, 69% for 34.62.82[.]165.
  • Tool B:
    • It uses the same five full identities.
    • In each period, the IPs' lists of paths that got a 4xx are exactly the same.
  • Kept separate: addresses that look similar but do not meet these criteria, for example two scans on 31 October and 2 November (42–43% of the core).

Definition of the core: paths seen in at least 70% of Tool A's 100 target scans. The list does not change for any threshold between 50% and 80%.

Reading notes:

  • Alert counts only cover requests that passed the WAF and got a 4xx, so they vary with the target and its WAF configuration.
  • "First seen" is the day a path first appeared in Tool A's records. Other tools may have tried the same path earlier; for example, /.cursor/mcp.json was also in the third tool's list in August.
  • On 22 August, a single line from an internal address whose real client we do not know also carries the same truncated identity. So 18 September is the earliest trace we can tie to an IP address, not a definite start.
  • We do not know who runs these scans; this post makes no attribution claim. These addresses may belong to virtual machines rented by the attacker. This is not a Google Cloud vulnerability.

References


This post is based on observations of real traffic in the SOC operated by 113SEC. For questions, or to share what you see in your own logs: info@113sec.com

FAQ

Which files does this scanner look for?

Every scan uses a fixed 112-path core list: .env files, Git and AWS credentials, Docker and backup files, and AI files such as .claude/settings.json, .env.anthropic and .env.openai. Most scans add a small rotating theme; with the themes, the tool tried 32 AI-assistant, LLM-platform and MCP paths.

How can I tell whether my site is exposed to these scans?

Run the 30-second self-test in this post: every sensitive path should return 404 or 403. If you get a 200, compare the response size with your home page. If the size is the same, you are in the SPA trap; if it differs, the file may really be public, so remove it at once and rotate the keys inside it.

Is blocking the IP addresses enough?

No. Half of the scans finish in under 2.5 seconds, and cloud IP addresses are reassigned often. An edge WAF rule that blocks dot-prefixed path segments, a detection rule that looks for the behavior signature in WAF logs, and a WAF IP list kept for at least 7 days work better together. For us, a 7-day list would have cut 31 of the 41 returning sessions.