TL;DR
Stolen cloud keys are mostly used to send messages. 72% of the 155 AWS keys that attackers used went straight to SES/SNS: send quotas, verified senders, SMS settings. Exfiltration barely registers (S3: 5%).
When a stolen credential looked like it could send, they tried to send. Attackers logged in to my fake mail server with the bait SMTP login and tried to push phishing campaigns through it. The fake server accepted every message and delivered none. The campaigns imitated Belfius bank (Belgium), a Greek “update your details” lure, and a Portuguese “account at risk” warning. There were also 419 advance-fee scams.
LLMjacking went from a side business to half the market. 12% of keys first used in August were checked for Bedrock. In September and October it was about half, spread across about 20 unrelated networks.
Median 37 minutes from theft to first use (n=103). A third arrive within 5 minutes. The slowest took 37 days.
The people who steal keys are rarely the people who use them. Only 12% of keys were first used from the IP that stole them. 82% were used from more than one IP; one key was tried from 1,691 addresses. 96% of the IPs that used a key never touched the honeypot at all.
Roughly 1 in 3 stolen keys gets used. That held steady across four months.
One stolen key, followed for 18 days: 75 IPs in more than 20 countries, from a quick “can it send?” check to a full inventory of the cloud account, and back two weeks later for the storage buckets.
Stolen AI keys get graded for resale. A checker tested five AI providers’ keys in five minutes, from 20 home connections in 14 countries, and slipped in questions designed to catch a fake endpoint like mine.
The exploit traffic was a botnet recruiting. 1,170 attempts from 161 IPs carried the same self-spreading installer. Others tried to install a cryptominer or steal the server’s own cloud credentials.
The attackers are cloud tenants; the people using the keys look like home users. 85% of scanning IPs were datacenter hosts. 74% of the IPs that used a stolen key were residential broadband, and 81% were in the US.
What this was
For four months, from July 7 to November 7, 2026, I ran a server that looked like a careless developer’s box. It had a walkable .git directory that rebuilt into a repo full of secrets, and the usual spread of .env, config, backup and credential files. It also ran a fake mail server, and fake AI inference endpoints that accepted any API key.
Every secret in it was a tripwire. The AWS keys were real keys belonging to nothing, and each one alerted from inside AWS the moment anyone used it. The more than 500 AWS keys rotated through the pool were mostly handed out one per scanner, so each use could be traced back to the moment, IP and TLS fingerprint of the theft. The SMTP credentials pointed at a fake mail server I controlled. It accepted any login and any message, recorded everything, and never delivered or relayed a single email. It made no outbound connections at all.
I retired it on November 7. This is everything the logs taught me.
The dataset
Window: July 7 → November 7, 2026 (124 days)
HTTP requests: about 950,000
Source IPs (Aug 7 – Nov 7): 8,606, of which 1,572 (18%) were benign research scanners
AWS canary keys handed out: at least 413
AWS canary keys used: 155, plus 1 shared static key
Distinct IPs that used a key: 12,353
SMTP sessions against the fake mail server: 18,701 from 2,188 IPs
Sessions that logged in with the bait SMTP credentials: 3,906
Two caveats that apply to everything below.
The canary keys had no permissions. Every API call an attacker made failed. So this dataset records what they tried first. It never shows what they would have done after succeeding. That’s exactly why the SMTP data matters: it’s the one channel where the stolen credential appeared to work, so attackers moved on to their next step.
API counts are lower bounds. The burn ledger records the first call from each distinct IP on each key. A burner that ran five calls from one address shows up as its first call.
Finding 1: it’s a messaging economy
Counting distinct keys, so one noisy replay loop can’t dominate:
GetCallerIdentity(STS): 131 keysGetSendQuota(SES): 93GetSMSAttributes(SNS): 74ListFoundationModels(Bedrock): 41CreateUser(IAM): 35Converse(Bedrock): 28PutUserPolicy(IAM): 25ListIdentities(SES): 23ListVerifiedEmailAddresses(SES): 22GetAccount(SES v2): 17InvokeModel(Bedrock): 11ListBuckets(S3): 7
Grouped by what the attacker was after:
SES / SNS, “can this account send?”: 111 keys (72%)
IAM persistence or permission mapping: 75 (48%)
Bedrock, i.e. LLMjacking: 56 (36%)
EC2: 14 (9%)
S3: 8 (5%)
Nothing past
GetCallerIdentity: 23 (15%)
The dominant use of a stolen AWS key is not data theft. It’s checking whether your account can push mail and texts, and how many.
Volume says the same thing even more loudly. Of the 20,798 distinct (key, IP) first calls, 11,006 were GetSendQuota and 6,393 were GetSMSAttributes: 84% of all burn traffic.
Finding 2: when the credential seemed to work, they tried to send phishing
The AWS canaries can only show intent. The SMTP bait showed what happens next.
The bait .env files carried SMTP credentials for a fake mail host I ran. It answered like a real mail server and replied 250 Ok: queued to every message. In reality it recorded the session and threw the message away. Nothing it received was ever delivered to anyone.
103 IPs logged in with the bait credentials, across 3,906 sessions. 205 IPs submitted message bodies. Twenty of the IPs that logged in also used an AWS canary key, so it’s the same population working through both kinds of stolen credential.
What they tried to send, by subject line:
“Ενημερώστε τα στοιχεία…” (”Update your details”): 1,616 sessions. Greek credential phishing.
“Action requise : confirmez votre accès Belfius”: 755. Phishing aimed at customers of Belfius, a Belgian bank.
“REPLY NOW AND RECEIVE YOUR APPROVED … FUND”: 318. A 419 advance-fee scam.
“RECEIVE YOUR CONSIGNMENT BOXES WORTH $9.5M USD”: 238. Also 419.
“SCAM VICTIM COMPENSATION”: 135. The 419 “recovery scam” variant, aimed at people who were already scammed once.
“Ação obrigatória — conta em risco” / “ATUALIZAÇÃO OBRIGATÓRIA”: 76. A Portuguese account-takeover lure.
The server’s own IP,
test smtp <ip>,asds: about 470. Relay tests before use.valid <host>|587|<user>|<password>: 159 messages from 12 IPs. A credential checker reporting its own success.
That last one is the whole credential pipeline in one line. A credential checker is a tool that takes a pile of stolen logins and tests which ones work. Here is what it did with ours:
Theft. A scanner grabbed a bait
.envfile holding the mail server’s address, port, username and password.Test. The checker logged in to the mail server with the stolen username and password. My server accepted it.
Report. To prove the login can send, it emailed its operator through that same server, with the working login as the subject line:
valid <host>|587|<user>|<password>.
That host|port|user|password line is the format stolen SMTP logins are sold in, as text files thousands of lines long. The subject line is the product being sold. Between August and November, 12 different IPs did this 159 times. In 158 of those sessions the checker logged in first, so each message is a complete test of a stolen credential followed by its report. None of the reports arrived, like every other message.
Who they were trying to reach
Attackers addressed 63,268 distinct recipients across 7,015 domains. None of these messages were delivered. Who they picked still shows how the economy targets people.
Half the target list was one ISP. The ten most-targeted domains, by distinct addresses:
verizon.net: 31,869 addresses (50.4%)
yahoo.com: 7,973 (12.6%)
aol.com: 2,479 (3.9%)
hotmail.com: 2,274 (3.6%)
icloud.com: 1,536 (2.4%)
gmail.com: 743 (1.2%)
comcast.net: 599 (0.9%)
yahoo.gr: 570 (0.9%)
msn.com: 493 (0.8%)
web.de: 378 (0.6%)
By mailbox provider, 69% of all targets were Yahoo-operated (Yahoo, AOL, and verizon.net, which Yahoo runs). Microsoft had 6%, US cable and telco ISPs 3%, Apple 3%, and Google only 1%.
That’s not a natural distribution, and it isn’t many independent spammers converging either. Three sending IPs account for 94% of all recipients. The verizon.net block came from two of them over five days in September and October, in full 100-recipient batches: a 419 advance-fee operation working through one bought list. Old ISP and Yahoo/AOL addresses are what such lists are full of. They’re the long-lived inboxes of an older population, which is exactly who 419 and “scam victim compensation” lures target.
The phishing campaigns were national, not bulk. Take out the three bulk senders and the picture flips to small, country-targeted runs. Each one pairs the language of the lure with that country’s local mail providers.
Greek “update your details”: 1,931 recipients, mostly yahoo.gr, otenet.gr and hotmail.gr (Greek ISPs and webmail).
Belfius bank: 695 recipients, mostly telenet.be, pandora.be and skynet.be (Belgian ISPs).
Portuguese “account at risk”: 76 recipients at sapo.pt, live.com.pt, iol.pt and netcabo.pt.
419 advance-fee (the bulk sender): 59,756 recipients at verizon.net, yahoo.com, aol.com, hotmail.com and icloud.com.
Greek and Belgian targets are a rounding error in the bulk list, but they’re the whole point of those two campaigns. The regional campaigns aimed at customers of a specific bank or national webmail. The bulk sender aimed at anyone on an old US address list.
The most-messaged addresses were the attackers’ own. Measured by how many different senders hit a domain, the leaders were gmail.com (117 of 205 sending IPs), tiscali.it (112) and hotmail.com (80). Those numbers don’t come from victims. Eight addresses were each reached by five or more unrelated sending IPs, and one tiscali.it address alone by 111. These are operator dropboxes: the inboxes where relay tests and valid … checker reports were sent. 506 sessions from 153 IPs were addressed only to these eight inboxes. In other words, a quarter of everyone who submitted a message was testing the relay or reporting a working credential home, not phishing.
Put Finding 1 and Finding 2 together and the economy is clear. The stolen credentials that matter most are the ones that can send. That means your SES quota, your SNS SMS settings, and the SMTP password in your .env.
Finding 3: LLMjacking grew from a side business to half the market
How Bedrock interest changed, by the month each key was first used:
July: 15 keys. 5 tried Bedrock (33%), 10 tried SES.
August: 49 keys. Only 6 tried Bedrock (12%); 41 tried SES (84%).
September: 46 keys. 22 tried Bedrock (48%), 10 of them reaching an actual inference call; 32 tried SES (70%).
October: 37 keys. 19 tried Bedrock (51%), 14 reaching inference; 21 tried SES (57%).
November (first 7 days only): 8 keys. 4 tried Bedrock, 6 tried SES.
August’s low Bedrock share is partly one actor. HORNET, a smishing crew (Finding 8), accounted for 17 of that month’s 41 SES keys. September and October are the opposite: the Bedrock keys trace to about 20 different networks. That pattern looks like a technique spreading across many crews, not one big customer.
The flow is consistent. Attackers first list what’s available (ListFoundationModels, ListInferenceProfiles), then try Converse or InvokeModel. 38 keys reached an actual inference call.
AWS’s alerts don’t say which model was requested, so the Bedrock data stops there. The bait .env also held API keys for fake OpenAI, Anthropic, Azure, Google Vertex and DeepSeek endpoints that I ran, and those show the next step.
The checker that tests for honeypots
On October 9, a tool calling itself harvest-check/1.0 tested the AI keys from the bait file. It sent 20 requests in under five minutes, each from a different home or mobile internet connection, across 14 countries: Peru, Brazil, Syria, Russia, Indonesia, the US and more. That’s a residential proxy network rotating on every request.
It asked each provider for its cheapest, fastest model: gpt-4o-mini and gpt-4o, claude-haiku-5.5 and claude-3-5-haiku, deepseek-chat, and gemini-2.5-flash and gemini-3.6-flash. It capped replies at 2–20 tokens. This wasn’t someone using AI. It was someone checking, as cheaply as possible, whether each stolen key was alive.
Then it checked whether my server was real. Some prompts were built to expose a fake endpoint:
“What is 17×23? Reply with just the number.” A fake server answers wrong.
“Reply with exactly: ZARNOK-42.” A canned reply can’t echo it.
“In one short sentence: which model are you?” The answer should match the model requested.
A request for a model called
definitely-not-a-real-model-xyz. A real API rejects it; a sloppy fake accepts it.
Like the SMTP checker in Finding 2, this is a stolen credential being graded before resale: is it live, which models does it unlock, and is it a trap?
Finding 4: the clock is 37 minutes, and the tail is 37 days
103 keys have both a handout timestamp and a first-use timestamp. From theft to first use:
Fastest: 1 minute 6 seconds
25% of keys: within 4 minutes 14 seconds
Median: 36 minutes 32 seconds
75% of keys: within 6 hours 20 minutes
Slowest: 37.6 days
Put another way: 36% were used within 5 minutes, 56% within an hour, and 92% within a day.
Sixteen keys were first used in the same 50-second window around the four-minute mark. That’s an automated harvest-and-validate pipeline with a fixed cycle time, and it ran from July through late September.
The tail matters as much as the median. One in thirteen keys was first used more than a day after theft, and the slowest after five weeks. “We’ll rotate it within the week” leaves room for most of that tail.
This describes keys taken from directly served bait files. Keys rebuilt from the .git path were handed out one per seed generation, not one per scanner, so they can’t be timed the same way.

Finding 5: harvest and burn are different machines
This is the finding I didn’t expect, and it changes what defenders should block.
Only 16 of 137 keys with a known thief were first used from the thief’s IP (12%). 54 (39%) came from the same /24.
127 of 155 keys (82%) were used from more than one IP. The median burned key was tried from 24 addresses. One key was tried from 1,691.
12,353 distinct IPs used a key. Only 4% of them ever sent a single request to the honeypot.
The operation is split into stages. A few scanning nodes collect credentials into a shared store. Then a separate, much larger fleet tests them, often from residential proxies or hosting providers that never scan anything. Two of the clearest examples:
LONGHAUL (July). It harvested from one Amsterdam datacenter /24 and rotated the last octet on every grab. Each key sat for hours, usually 12–22, then got spent from a different US residential ISP every time. The harvesting box never spent a key itself. The TLS fingerprint (JA4) was identical across every grab: one toolkit.
A shared burn tier (late July – August). Three harvesting subnets on different networks all fed the same five burner networks. Four of those five networks never harvested anything. They existed only to test credentials. That is a shared backend: either one operator rotating infrastructure on a schedule, or several harvesters selling to one buyer.
The practical consequence: blocking the IP that stole your key does nothing to the IP that uses it. Reputation feeds are behind, too. HORNET, a smishing crew working from one French-registered network, barely registered in GreyNoise or AbuseIPDB while it was most active in August. By November, AbuseIPDB had flagged about a third of its addresses (272 of 768). And 90% of the IPs that used stolen keys scored under 25 out of 100 on AbuseIPDB (Finding 8). The only reliable defense is the one at the far end: detect the exposure and kill the credential.
Finding 6: the life of one stolen key
Aggregates flatten the story. Here is one AWS key, start to finish. Every row is a real call from AWS’s own logs.
Hour 0. A server in a Bulgarian datacenter grabs the bait
.envfile.Hour 6. Someone checks whether the account can send email (
ListIdentities) from a US hosting server. Six minutes later, a Canadian server tries to create a new IAM user: an attempt to give themselves a backdoor login.Hour 34. Seven calls in about a minute, from six countries: the Philippines, Indonesia, Russia, the US, Turkey and Qatar. Send quota, send statistics, SMS settings, the list of AI models, and an AI request (
Converse). This is the grading pass: what can this key do, and what is it worth?Hour 45. A full inventory of the account. Secrets Manager, Parameter Store, databases, DynamoDB tables, Lambda functions, container registries, SNS topics, Cognito user pools, KMS keys, EC2 instances. Eleven calls in under four minutes, from eight countries. Someone is mapping everything in the account worth stealing.
Day 14. It comes back from Ireland and Germany for the storage buckets (
ListBuckets) and the account’s other access keys.Day 18.7. Last use.
In total: 75 calls from 75 different IPs, more than 20 different API calls, and not one from the server that stole it.
That inventory sweep wasn’t a one-off. On October 10, the same pattern ran across five different stolen keys on the same day. Each key got its sweep of 9–10 services in one to ten minutes, from IPs in 27 countries overall. Two of those keys were stolen eight days apart, from different networks, yet were swept within hours of each other. That looks like a batch of keys sold or handed to a single buyer.
The long tail is real, too. Another key was still being checked for AI model access 66 days after it was stolen. Along the way it had tried InvokeModel (day 29) and temporary credentials (GetFederationToken, day 30).
Where the loot goes: Telegram
Twice, a link that existed only inside stolen material was fetched by Telegram’s link-preview service, from Telegram’s own network:
A stolen log file. One bait file was a Laravel application log with a tracking link inside it. A Bulgarian datacenter server downloaded it on September 26. 20.5 hours later, Telegram fetched that link.
A stolen AI key. On September 26, a Dutch datacenter server tested a stolen fake Azure OpenAI key against my endpoint. Three days later, Telegram fetched that endpoint’s URL. On October 21 the same key was tested again, from a Moldovan host, by a tool with the same fingerprint.
Telegram fetches a link like this when it appears in a Telegram message. I can’t tell whether a person pasted it or a tool posted it automatically; many credential stealers and checkers report their results to Telegram bots. Either way, stolen material reached Telegram within one to three days.
Two events is an observation, not a rate. And plenty of scanners pretend to be Telegram’s bot: about 900 requests used its name from Google Cloud servers. Only requests from Telegram’s own network count here, and there were exactly two.
Finding 7: what they actually came for
Bait taken, August 7 – November 7 (n=10,012 grabs from 942 IPs):
/.git/config: 1,301 grabs/.env: 442/backup.sql: 175/.git-credentials: 139/wp-config.php: 135/.env.local: 115/.env.bak: 114/.env.production: 107
.git/config is the single most-grabbed file, but it’s the door, not the goal. Add up the .env variants (.local, .bak, .production, .backup, .old, .save, .staging…) and the family totals 2,978 grabs against 1,534 for all of .git, about 2:1. Secrets files beat source code.
About a third of stolen keys get used. Of keys handed out before October 1, so each had at least five weeks to be used, 98 of 293 (33%) were. Most theft is indiscriminate hoarding; the third that does get used is worked by the automation in Finding 4.
Finding 8: who the attackers are
Across August 7 – November 7, 7,028 IPs behaved like attackers, after the research scanners were removed. They split into two populations that barely overlap: the IPs that scan and steal, and the IPs that use what was stolen.
The thieves: rented cloud servers, briefly
85% of attacker IPs were datacenter or web-hosting servers. Only 11% were home or business broadband, and the remaining 4% were mobile, CDN or unknown.
The largest single sources were mainstream clouds, not exotic bulletproof hosts. In order: DigitalOcean, Google Cloud, a French-registered hosting network (HORNET’s), Tencent, Microsoft, Alibaba and Amazon. Overall they spanned 586 networks in more than 100 countries. The US led with 36%, then the Netherlands 9%, France 7%, Germany 6%, China 5%, the UK 5% and Belgium 5%.
They’re disposable. 75% of attacker IPs showed up on exactly one day and never came back. Only 126 were active on seven or more days, and only 10 on thirty or more. A blocklist built from yesterday’s scanners mostly lists addresses that have already moved on.
They dress up as browsers. 70% of attacker IPs sent a browser-style User-Agent. Python clients were 13%, an empty User-Agent 8%, the zgrab scanner 5%, curl 4% and Go 3%. The TLS fingerprints tell the truth: the top five JA4 hashes cover 28% of fingerprinted attacker IPs. “Chrome on Windows” with a Python TLS stack is a scanner, not a person.
The funnel narrows fast. Of 7,028 IPs that probed, 881 (13%) took a bait file and 455 (6.5%) dumped the .git directory. Most scanners check a short list and move on. A small minority does the whole job.
The key users: home broadband, in the US
The 12,353 IPs that actually used a stolen key look nothing like the thieves.
74% were home or business broadband connections. 25% were datacenters, and 1% mobile.
81% were in the United States. France had 4%, the UK and Germany 3% each, and the Netherlands 2%.
Most of that “residential” traffic comes from a handful of small US broadband and IX networks, with thousands of addresses each. That’s the signature of residential proxy services: stolen keys get tested from addresses that look like ordinary American homes, because a US residential IP calling AWS draws the least suspicion. None of the 12,353 came through Tor. Tor exit nodes are too well known to be useful for this.
Reputation feeds barely know them. 27% of key-using IPs had an AbuseIPDB score of exactly zero, and only 4% scored 50 or more. Compare the thieves: 66% of scanning IPs scored 50+ on AbuseIPDB. Scanning is noisy and gets reported. Using a stolen key is one quiet API call from a home IP.
The tooling is ordinary. Where the client identified itself, it was the official AWS tooling: Python’s boto3 and the AWS CLI, mostly on Windows Server and Linux. The most common single build was boto3 on Windows Server 2022. A few used purpose-built recon tools. Nothing exotic is needed: a stolen key plus the standard SDK is the whole kit.
A few of the crews
Aggregates hide the operations. Five were distinct enough to name. The codenames are mine, and none refers to a hosting provider:
LONGHAUL. Harvested from one Amsterdam datacenter /24, rotating the last octet on every grab. Every key was spent hours later, usually 12–22, from a different US residential ISP, aimed at Bedrock. Its harvesting servers never used a key themselves.
HORNET. 768 IPs on one French-registered network, up to 134 in a single /24, harvesting around the clock. Its keys went straight to SNS: SMS settings, sandbox status, origination numbers. Text-message fraud, not email.
QUICKDRAW. One Chicago VPS that stole a key and used it from the same IP four minutes later. No laundering, no split; just speed.
MAGPIE. Six disposable Google Cloud VMs running an identical .env dictionary in lockstep. It collected keys and rarely used them: a hoarder.
HOMEBREW. Used a stolen key for Bedrock inference from a residential ISP in Tennessee, with a hand-rolled async HTTP client instead of the AWS SDK. Its IP never appears in my surviving request logs.
Finding 9: the background noise
Most of what hits an exposed server has nothing to do with your secrets.
18% of source IPs are research scanners (Censys, Shadowserver, university crawlers). They can be separated by reverse DNS, ASN and GreyNoise. If someone quotes raw unique IPs as “attackers,” they’re overcounting by about a fifth.
One brute-forcer was 32% of all traffic. Four IPs on one Canadian hosting provider sent 283,000 authenticated
GET /managerequests over eight days in October and did nothing else. Without removing them, October looks like a 3× surge in attacks.The exploit probes were for real, recent CVEs. Distinct IPs probing each one:
phpunit
eval-stdin(CVE-2017-9841): 188 IPs, still the most common after nine years.PHP-CGI argument injection (CVE-2024-4577): 178 IPs.
SAP NetWeaver (CVE-2025-31324): 145 IPs.
Exposed Docker Engine API: 101 IPs.
Langflow (CVE-2025-3248): 84 IPs.
Fake AI inference endpoints drew almost nothing. 45 requests from 26 IPs in four months, against thousands of
.envgrabs. Attackers don’t hunt for exposed model servers. They steal the API keys and go to the provider directly.
Finding 10: what the exploits tried to install
The fake-vulnerable endpoints pretended to be exploitable, so attackers sent their real payloads. 3,602 exploit attempts from 612 IPs. About half were probes that only checked whether the server was vulnerable. The rest carried a payload, and the payloads fall into four groups.
1. A self-spreading botnet (most of it). 1,170 attempts from 161 IPs over 67 days, mostly through the PHP-CGI flaw (CVE-2024-4577), all carrying the same installer. Decoded, it:
moves into a temp directory (
/tmp,/var/tmpor/dev/shm);writes out an embedded SSH private key and copies a second-stage script from the operator’s server over SSH;
falls back to plain
wgetorcurlif SSH is blocked;runs it with a tag naming the exploit that got in, such as
cve_2024_4577.selfreporphpunit.selfrep;deletes the key and script behind it.
The .selfrep tag is the tell: each infected server goes on to scan for and infect the next one, and the operator tracks which exploit recruited which machine. The 161 IPs attacking me were very likely earlier victims, already recruited and now recruiting. All of it pulled from three servers in the same address range, each used for about a month before the next took over.
2. A cryptominer. In the last week of the run, 200 attempts from 15 IPs tried to install an XMRig Monero miner. It came in through several code-execution endpoints, including a Langflow exploit (CVE-2025-3248) and a forged Kubernetes ingress admission request. The script is careful: it checks whether the miner is already running before installing again, installs curl if it’s missing, and hides its files in a dot-directory in the user’s home folder. The file names (dota3, .configrc) match those used by the long-running “Outlaw” mining botnet.
3. Credential theft through a webshell. One attacker’s PHP payload didn’t install anything. It searched up to seven parent directories for a .env file and also read the process environment, base64-encoded everything, and printed it in the response. Remote code execution, used as a faster way to steal the same secrets as everyone else.
4. Stealing the server’s own cloud credentials. 1,948 requests from 22 IPs tried to make my server fetch 169.254.169.254/latest/meta-data/iam/security-credentials/, through endpoints named /fetch, /proxy, /api/preview and /image. That address is the cloud metadata service. On a real cloud server it hands out temporary credentials for the server’s own role. This is the server-side request forgery (SSRF) route to the same prize: cloud keys. One Google Cloud-hosted scanner disguised itself as Telegram’s link-preview bot, presumably because link previewers are usually allowed to fetch arbitrary URLs.
Seven hunt hypotheses
Each one comes from observed behavior in this dataset.
H1: SES/SNS reconnaissance by a principal with no messaging history. GetSendQuota, GetSMSAttributes, ListVerifiedEmailAddresses, ListIdentities from a principal that has never called them. 72% of stolen keys did this. Legitimate workloads call these on a stable schedule, so the false-positive rate is low.
H2: Bedrock model discovery from a principal with no Bedrock history. ListFoundationModels / ListInferenceProfiles appeared on 41 keys. These are control-plane calls, so you can catch the reconnaissance even if you haven’t enabled Bedrock data events. Check that your trail records them. Separately, InvokeModel and Converse are data events. If you haven’t enabled those, a denied inference call leaves no record.
H3: One access key, many source IPs, short window. 82% of used keys were tried from more than one IP. A median of 24 addresses on one key is not a pattern legitimate automation produces.
H4: Exposure-to-first-use correlation on a 1-hour window, with a 30-day lookback. Join secret-scanning alerts to CloudTrail first use. An hour catches 56%; 24 hours catches 92%. Keep watching for a month for the long tail.
H5: IAM self-modification by a non-admin principal. CreateUser / PutUserPolicy on 38% of stolen keys. This should be near zero in a mature environment, which makes it an alert candidate, not just a hunt.
H6: SMTP authentication from new sources, followed by a sudden jump in recipient count. For any SMTP credential that has ever lived in a config file: a new source IP, a relay test (a message to yourself or a one-word subject), then volume.
H7: Server-side fetches of the metadata address. Any outbound request from an application server to 169.254.169.254, or an inbound request carrying it in a url= parameter. Attackers tried this 1,948 times. Enforce IMDSv2 so a simple GET returns nothing, and alert on the attempt.
What to do with this
Treat messaging credentials as tier-1 secrets. SES, SNS and SMTP credentials are what attackers check first and use when they work.
Rotation is measured in minutes, and monitoring in weeks. A third of keys get used within 5 minutes. One in thirteen gets used after more than a day.
Don’t expect to block the key user. The thief and the user are different machines on different networks, and reputation feeds miss many of them. Detect the exposure and kill the credential.
Check that Bedrock activity is in your CloudTrail before concluding you’re clean.
Put canaries in the files that leak. Everything above exists because fake credentials report back.





