In order to discover what would happen we created two honeypot LLM gateways: one mirroring LiteLLM and another one mirroring a generic OpenAI-compatible endpoint. We then proceeded to create over 40 individual organizations, each with at least on Github repository in a way that was convincing to threat actors. API keys along with the addresses of the gateways were added to these repositories in such a way that it looked like they were added by mistake by AI or human error. From this we observed over 3 million requests to our gateways, most of which were probing attacks and some of which ended up using inference in a pattern that we identified as reseller/gateway activity. Some connected to harnesses like OMP or Hermes, others connected to Telegram agents and some were even directly sourced from black market reseller domains directly.
The experiment
How the keys were exposed
For realism we initially created GitHub projects spanning a wide array of types: ticket triage, document indexing, agent tools, and other AI applications. To make our accidental leaks seem realistic, we included a working key and our gateway’s URL in code examples in the repositories.
In order to make our honeypot more attractive we advertised multiple SOTA models including astra, fable, opus 5 and sol. The attacker prompts were routed to hosted open-weight models in order to control costs while providing a believable attack surface. We also included guardrails that ensured model specific questions wouldn't reveal the true identity of our model and instead provide a facade that they were actually interacting with a SOTA frontier model from a company gateway.
We ran New York gateway for 36 days; San Francisco ran for nine as it was set up later, so the totals weren't equal as one had more time to collect "users".
The keys were released in waves in order to be more realistic. The table below shows us how long the keys were publically available to attackers.
| Exposure time | Keys |
|---|---|
| Less than a day | 19 |
| One to seven days | 91 |
| More than a week | 64 |
| No precise publication time | 5 |
What we tracked
To ensure that we could extract as much from our honeypot as possbile we recorded as much signal las possible including: the key used, headers, referrers, ip, geoip, route, model, prompts, http status and more. We also saved every prompt and its response for further analysis.
The snapshot contains traffic from both gateways. Here is what the three counts mean:
- 1,605,260 saved request captures across all endpoints.
- 1,417,507 captures from prompt endpoints, including rejected requests.
- 9,375 prompt requests using keys matched to our 179 documented leaks, counted in the request logs.
The 9,375 figure is the documented-leak subset, not the total prompt volume. Most captured traffic used credentials we could not match to those publication records.
Published-key use
Which leaked keys were used?
In total we published 179 keys. Outside requests used 65 to fetch the model list and 18 to send prompts. Requests using 16 keys returned HTTP success. We compared the projects and leak locations to investigate the difference.
A model-list request checked the advertised catalog; a prompt request tried to use a model. In 82 IP/key pairs, the list check preceded the first successful prompt. The median gap was 37 seconds, measured between API requests, not from publication.
Leak location and project type
Which leak locations drew requests
We compared the projects, the files containing the keys, and the time each key had been public. The table covers three batches of 40 repositories. To give each key the same observation period, we counted only its first 24 hours after release.
| Repository batch | Keys released | Keys checked | Keys used for prompts |
|---|---|---|---|
| Early developer projects · NYC | 40 | 38 | 7 |
| Utility projects · NYC | 40 | 12 | 4 |
| Utility projects · SFO | 40 | 5 | 1 |
In the early batch, one source checked 37 keys within a minute of each release. These were model-list requests. The entire batch produced only seven prompt requests, all from another source repeating the same message. Broad collection did not lead to sustained use of every key it found.
The two utility batches let us hold several things constant. All 80 projects were Python tools, advertised gpt-6-sol, and contained a key beside the gateway URL in config/development.env. Yet only 17 keys were checked and five were used for prompts. The other 63 never appeared in our gateway logs during more than four days of observation.
The four New York utility keys used for prompts came from api-response-code-map, http-header-safety-check, pagination-field-check, and operation-id-audit. San Francisco’s came from cors-wildcard-credentials-check. They were all API-related tools. These projects differed in release order and organization, so we cannot tell whether their subject matter drove the selection.
File format may matter too. Six of the early batch’s seven prompt-tested keys came from Go projects with JSON configuration; one came from a Node project, and none from its 14 Python projects. All seven prompt requests came from one source. Language and file format changed together, so we cannot tell which affected that source’s choice.
We also tried other leak locations. A GitHub Gist key received eight prompt requests. Keys in two documentation pages and two downloadable starter archives received no gateway requests over roughly six days. One of six keys exposed in commit history was checked and used for prompt attempts. Those small groups show which surfaces produced observable use, but not which were scanned.
The newest keys had less time to be found: 19 had been public for less than a day. But 91 keys had at least three days of exposure without a request. All 64 keys whose first request we could time were checked within six hours of release. We did not retain repository crawl or download telemetry, so we cannot distinguish a key that was never collected from one that was collected and ignored.
Once a few keys were adopted, their repeated use dominated the traffic. Six keys accounted for 98.9% of prompt requests using our leaked credentials. Repeated use of these keys drove the request volume.
Requests with other credentials
The arithmetic question behind most captured prompts
A request can carry any string as its API key. We recorded those attempts even when authentication failed, so a saved prompt does not necessarily mean the sender had a working key. Many attackers tried to brute force over 100+ admin keys that didn't work. This relates to an original vulnerability with LiteLLM where many gateways shipped with a default admin key
What the repeated credential tests looked like
Prompt requests carried 93,100 nonempty credential values outside our publication records. Of those, 78,130 were only 1–16 characters long. 93.7% of prompt requests outside the publication match returned HTTP 401. Almost all of these attempts carried curl-labelled headers.
The clearest repeated pattern was this message:
What is 483*927+1145? Answer with only the final number.We captured 1,401,673 copies across 92,596 supplied credential values and 20 requested model names. None of those values matched our documented leaks. Replaying a fixed question while changing the token and model is consistent with automated credential and model testing.
Repeated use of our leaked keys
A key check described how a relay collected API keys
This source made 1,215 prompt requests using our leaked keys. Within that traffic, we recorded 21 copies of one exact message. We’re highlighting it because it described how a relay collected and managed API keys. It began:
“The relay pool keeps a small SQLite database of API keys collected from public channels.”
The prompt described a “relay pool”: keys and API connections managed by a gateway. The system would retry keys that had hit their quota once an hour, test all the keys every six hours, and update the gateway’s routing each day. It would choose connections based on throughput, response time, and account balance, and could block collection sources by chat title.
After that description, the only instruction to the model was Ответь одним словом: OK—“Answer with one word: OK.” The requests also carried the software label keypool in their user-agent header.
Of those requests, 12 returned HTTP 200, 7 returned 429, and 2 returned 404.
This is the system described in the submitted prompt; we did not inspect the collector’s code.
Gateway connections
Following the traffic to an API relay
my.git.christmas resolved to an IP that was sending prompts to our API. Requests from that IP also mentioned the domain. Veridrop lists it as an OpenAI-compatible API relay.
Requests from the same IP referred to a fo2api deployment. Together, the DNS record and prompts linked the source to relay infrastructure, though they didn’t tell us how many users were behind it.
| Observation | Evidence |
|---|---|
| Requests to our API | 618 HTTP-successful prompt requests used our published keys. |
| DNS match | my.git.christmas resolved to the same address recorded in those requests when we checked. |
| Submitted context | Requests mentioned the domain and work on a fo2api deployment. |
The older logs retain a forwarded source address, without a separate socket-peer record. The DNS and context matches support the link; they do not identify an operator.
Relay development and testing
Troubleshooting a relay through our gateway
The sender also used our gateway to troubleshoot fo2api. One prompt asked a coding agent to fix a production JSON tool-schema error affecting ChatCompletions, Responses, and Anthropic routes, and pointed it to specific backend files to inspect.
From a chat export into a gateway’s key pool
Earlier turns asked the agent to check keys from a chat export, find ones that could serve StepFun models, and add the working ones to a Sub2API-based gateway. NaraRouter’s endpoint appeared in the export and in the testing output. A generation probe returned 402, and a later account listing marked that connection as an error.
The user then supplied an OpenLux endpoint and key, asking the agent to check it and add it to the API. The following tool output included model tests, retries, and routing mappings. A later listing showed the OpenLux account as active.
An APIMart key came next, along with a request for auto-glm, auto-kimi, auto-qwen, and auto-stepfun: model names that would route requests between the best working models in each family. The APIMart test output reported 402 errors for insufficient balance, and its account was listed as an error.
A separate history from another IP included the CLIProxyAPI README. That task was to configure a coding agent to send service_tier: "fast" through the proxy. OpenLux and APIMart also appeared in the README’s sponsor list; those links were incidental to the documentation lookup.
These were instructions and tool results inside submitted histories, not calls from those providers to our gateway.
Telegram bot traffic
What Telegram histories revealed about the traffic
Telegram message metadata and earlier replies appeared in 88 captured requests from three recorded source addresses, including the source linked to my.git.christmas.
Another of those addresses was in Helsinki on Xorek-labelled hosting, in the AEZA network. It sent 31 of the captures containing Telegram conversation metadata. The submitted histories included 12 distinct sender IDs marked as non-bot accounts. This is the same source discussed in the hosting section; the location describes its server, not the people in the conversations.
The reply history highlighted telegram bot named @obed2021bot, with the same account ID across all three sources. In its public profile it is called “Обед” and advertises GPT 6 Astra Max along with free access to whomever has access. The message metadata from this bots requests also included two fields: guest_query_id and guest_bot_caller_user: Telegram fields for guest-mode bot interactions. Those fields give us enough evidence that we can say that Telegram conversations were being passed through an inference gateway to our system.
After removing duplicate message metadata, we found 29 distinct sender IDs marked as non-bot accounts. They appeared in 16 private chats and 10 supergroups. These are IDs in the conversation histories, not verified users. We have omitted personal identifiers.
{
"format": "telegram_message_context/v1",
"message": {
"reply_to_message": {
"from": {
"is_bot": true,
"username": "obed2021bot"
}
}
}
}We couldn’t verify who operated the bot or whether our replies reached Telegram.
Browser testing
Testing our leaked keys from a browser
6 requests using our published keys carried a www.hvoyai.com referrer. Each returned HTTP 200 and used the default message in Hvoy’s current tester:
“Calculate 17 + 26. Reply with only the number.”
The tester takes an endpoint, key, and model name, then sends the request directly from the visitor’s browser. The matching prompt and browser headers point to visitor-side testing, not a request from Hvoy’s servers. We checked the current implementation; we don’t have a historical copy.
Following the test requests
A browser test led back to VPN infrastructure
Other requests from the IP behind the Hvoy tests included instructions for maintaining updates.listokdoma.ru. When we checked DNS, that domain and related subdomains pointed to the same IP. This links the recorded address to the VPN configuration in the prompts; it doesn’t link Hvoy to the VPN.
| Clue | Recorded evidence |
|---|---|
| Domain match | listokdoma.ru, manage.listokdoma.ru, and updates.listokdoma.ru resolved to the recorded IP when we checked. |
| VPN maintenance | 7 captured requests included system context naming updates.listokdoma.ru alongside Xray/3x-ui VPN setup and Nginx reverse-proxy maintenance. 3x-ui manages Xray proxies. |
| Coding agent | Requests carried omp/18.3.1 headers and context describing Oh My Pi configuration and model roles. |
| Session handling | Submitted instructions described skill and memory services that inject user_id, team_id, and agent_id from a session. |
| Request tools | Other requests from the same address carried browser and Python headers. |
Using our published keys, the same recorded address made 155 prompt requests; 132 returned HTTP success.
Asked to review a coding agent’s actions
Some prompts asked for a safety score for a coding-agent action, formatted as <severity>N</severity>. They included commands, browser output, project instructions, and configuration from the session being reviewed. The commands were review material, not instructions to execute on our gateway.
Hosting infrastructure
Where the leaked-key prompt traffic came from
Of the 9,375 prompt requests using our leaked keys, network lookups classified 8,058 (86%) as coming from hosting infrastructure. The largest groups included Xorek, Hetzner, and the PLAY2GO and Liteserver networks.
Requests using our leaked keys came from 53 recorded autonomous systems (ASNs)—network identifiers used for internet routing. Another 2 requests had no ASN. The table shows the eight largest groups; the complete breakdown is below.
| Network · exit countries | Requests · share | What the requests contained | Software fingerprints |
|---|---|---|---|
| PLAY2GO INTERNATIONAL LIMITEDAS215439Finland, Sweden, The Netherlands | 2,65628.3% of total |
|
Other headers · 408
|
| AEZA INTERNATIONAL LTDAS210644Finland | 2,20823.6% of total |
|
Other headers · 175
|
| LiteserverAS60404The Netherlands | 1,21513.0% of total |
|
|
| Hetzner Online GmbHAS24940Germany | 97210.4% of total |
|
|
| JSC SelectelAS50340Russia | 5215.6% of total |
|
|
| HOSTSLIM OUAS207083Estonia | 2282.4% of total |
|
Other headers · 43
|
| Blockchain Creek B.V.AS207994The Netherlands | 2012.1% of total |
|
|
| U1 DIGITAL SERVICES LTDAS213877Finland, Germany | 1651.8% of total |
|
Other headers · 13
|
Shares use all 9,375 leaked-key prompt requests. Activity counts describe captured inputs, include rejected requests, and can overlap. Software fingerprints are sender-supplied header labels.
HTTP 2xx counts successful request statuses. ASN holders, hosting brands, and the people using their services are separate identities.
All 53 recorded ASNs
| Recorded network | Exit locations | Prompt requests | HTTP 2xx |
|---|---|---|---|
| PLAY2GO INTERNATIONAL LIMITED AS215439 | Finland, Sweden, The Netherlands | 2,656 | 674 |
| AEZA INTERNATIONAL LTD AS210644 | Finland | 2,208 | 831 |
| Liteserver AS60404 | The Netherlands | 1,215 | 886 |
| Hetzner Online GmbH AS24940 | Germany | 972 | 689 |
| JSC Selectel AS50340 | Russia | 521 | 405 |
| HOSTSLIM OU AS207083 | Estonia | 228 | 165 |
| Blockchain Creek B.V. AS207994 | The Netherlands | 201 | 199 |
| U1 DIGITAL SERVICES LTD AS213877 | Finland, Germany | 165 | 142 |
| Verizon Business AS5650 | United States | 156 | 27 |
| AS203087 AS203087 | Kazakhstan | 151 | 105 |
| SERV.HOST GROUP LTD AS207957 | Poland | 137 | 0 |
| SMARTNET Germany GmbH AS203446 | France | 130 | 107 |
| OOO Post ltd AS12494 | Russia | 129 | 84 |
| DpkgSoft International Limited AS215590 | Finland | 86 | 37 |
| ZetServers AS25198 | France, Germany | 50 | 49 |
| IT7 Networks Inc AS25820 | United States | 48 | 43 |
| MSS LLC. AS44843 | Russia | 35 | 35 |
| ETERNITY INTERNATIONAL LIMITED AS212743 | Estonia, Germany, The Netherlands | 34 | 32 |
| Eons Data Communications Limited AS138997 | Japan | 20 | 19 |
| Amazon.com, Inc. AS16509 | Japan | 18 | 5 |
| M247 Europe SRL AS9009 | Belgium, United Kingdom | 17 | 17 |
| Cloudflare, Inc. AS13335 | Estonia, Germany, United States | 15 | 9 |
| Datacamp Limited AS212238 | Japan | 15 | 8 |
| GTHost AS63023 | Switzerland | 15 | 15 |
| Baxet Group Inc. AS26383 | United States | 13 | 4 |
| ISPCONNECTED CORP AS398712 | United States | 13 | 13 |
| PJSC Rostelecom AS12389 | Russia | 12 | 12 |
| MTS PJSC AS13055 | Russia | 12 | 12 |
| Black Mesa Corporation AS46997 | United States | 12 | 0 |
| OneAsiaHost AS59211 | Singapore | 10 | 10 |
| ALEXHOST SRL AS200019 | Switzerland, The Netherlands | 8 | 7 |
| Shenzhen Tencent Computer Systems Company Limited AS45090 | China | 7 | 7 |
| Oy Creanova Hosting Solutions Ltd. AS51765 | Finland | 7 | 7 |
| SKYDIGITAL TELECOM LTD AS214143 | France | 6 | 6 |
| Datagio Systems Ltd. AS218914 | Estonia | 6 | 6 |
| YES TELECOM TELECOMUNICAÇÕES LTDA AS267038 | Brazil | 6 | 6 |
| eStruxture Data Centers Inc. AS10929 | Canada | 5 | 5 |
| H2NEXUS CLOUD SERVICES - FZCO AS215730 | Germany | 5 | 5 |
| UAB Cherry Servers AS59642 | The Netherlands | 4 | 4 |
| NEXTTRANSLATE LTD AS203614 | Latvia | 3 | 3 |
| Advin Services LLC AS22295 | Germany | 3 | 3 |
| SkyNet Ltd. AS35807 | Russia | 3 | 3 |
| NLS Kazakhstan LLC AS200590 | Kazakhstan | 2 | 2 |
| Aztelekom LLC AS28787 | Azerbaijan | 2 | 0 |
| Orgtechservice Ltd AS56361 | Russia | 2 | 2 |
| Scalaxy B.V. AS58061 | Finland | 2 | 2 |
| FREEDOM TELECOM LLP AS58172 | Kazakhstan | 2 | 2 |
| ASN not recorded | France | 2 | 2 |
| GSL Networks Pty LTD AS137409 | Japan | 1 | 1 |
| OVH SAS AS16276 | United States | 1 | 1 |
| CONECTE MARECHAL AS270371 | Brazil | 1 | 1 |
| GVA Cote d'Ivoire SAS AS36924 | Uganda | 1 | 1 |
| PJSC Vimpelcom AS8402 | Russia | 1 | 1 |
| TE-AS AS8452 | Egypt | 1 | 0 |
The total is 9,375 prompt requests and 4,711 HTTP successes. These are cached source-network observations, including access networks and proxies; they are not 53 identified attackers or hosting businesses.
European servers, Russian payment options
PLAY2GO and Xorek both advertise hosting accessible through Russian payment systems. Together, their recorded sources accounted for 4,950 requests (52.8%) using our leaked keys. That groups their payment offerings, not their ownership.
PLAY2GO lists ruble and euro prices, and its public channel advertises Russian cards and SBP payments. Its current website names a Dubai company, PLAY2GO - FZCO. The UK filing for PLAY2GO International, the name in our ASN records, lists a Ukrainian controlling shareholder.
Xorek offers Moscow and European server locations with ruble pricing and MIR/SBP payments. Its website identifies DpkgSoft International as the operator; that company’s UK filing lists a Russian resident with at least 75% ownership.
A sanctioned network behind the Telegram-linked traffic
One source connected from an address geolocated to Finland on the network of AEZA International, a U.S.-sanctioned hosting company. The Xorek-labelled traffic spans two ASNs: 2,208 requests on AEZA International’s AS210644 and 86 on DpkgSoft’s AS215590. Treasury describes it as the UK branch of Russia-based AEZA Group.
That is a documented connection to a sanctioned network. Our logs do not show who paid for these servers or establish sanctions evasion by PLAY2GO or Xorek.
Rostelecom’s AS12389 also appeared in the logs, with 12 leaked-key prompt requests, all returning HTTP success. Its OFAC listing carries narrower restrictions: Directive 3 prohibits U.S. persons from dealing in specified new debt with a maturity longer than 14 days or new equity. AEZA’s SDN designation is broader: its property under U.S. jurisdiction is blocked, and U.S. persons are generally prohibited from dealing with the company.
Half the prompt traffic came from two Finnish addresses
Two recorded addresses geolocated to Finland sent 4,749 requests—51% of the leaked-key prompt traffic. One was the address linked to my.git.christmas on the PLAY2GO network. The other was on Xorek-labelled hosting and also sent the Telegram conversation histories described above.
The larger Xorek source sent 2,208 requests under 23 model names. It mixed short checks with longer requests: 830 contained coding-agent system instructions. The network address therefore covered both automated checks and agent activity; its Finland location does not tell us where the users were.
The same check ran on Xorek and Hetzner
Two recorded addresses repeatedly sent Reply exactly: ok: 67 requests from Xorek-labelled hosting and 315 from Hetzner. All 382 carried a Zoltraak-labelled header, with one user message, no streaming, and a request for only five or eight output tokens.
The checks covered 11 model names and 6 leaked keys. The Hetzner address sent them to both honeypots. The matching prompt, header and request settings suggest shared checking software; they do not establish that the two addresses had the same operator.
Gateway capability tests
5,000 requests to find out what our gateway could do
A server geolocated to Buffalo, New York, on ColoCrossing hosting kept asking our gateway to edit a Python file, identify a cat, and find a marker buried in a long input. It repeated these tasks under different model names, checking what the endpoint could do.
Across 27 days with recorded traffic, it sent 5,036 prompt requests under 18 model names, plus 368 model-list checks. 3,254 prompt requests returned HTTP success.
The requests carried AutoGenX-Engine/9.1 headers. Their system instructions described an “internal routing and logging system” serving models “via OpenRouter.” We found that description in 172 captured requests. It describes the sender’s setup; it does not establish that OpenRouter sent the traffic.
There were only 183 distinct user messages in the captures. The repeated tasks explain much of the volume:
| Test | What the prompt asked for | Saved checks |
|---|---|---|
| Model identity | Identify yourself precisely. Return a fixed JSON schema describing the model and its capabilities. | 813 |
| File editing | Change PORT from 8000 to 9090 in api_server.py and add TIMEOUT = 30. | 959 |
| Function calling | Invoke get_weather for the temperature in London. | 314 |
| Vision | Identify the animal in an embedded cat photograph and return only its name. | 260 |
| Long-context retrieval | Recover CTX_64K_OK, a marker hidden in a long block of repeated text. | 288 |
| Browsing | Look up a webpage’s title and return it as JSON. | 314 |
The coding test asked for a specific edit: change PORT from 8000 to 9090 and add TIMEOUT = 30. It supplied a Windows-style C:\work workspace and coding-agent instructions, then repeated the task under 17 model names. The histories contained no assistant or tool turns, so we could see the task being tested but not whether any resulting tool calls were executed.
Other tests checked images and long inputs. The roughly 77,000-character image payload was mostly an embedded JPEG. The retrieval tests hid a marker about 15% or 90% of the way through repeated filler text; the largest input contained 396,862 characters. In 283 requests, the sender asked for up to one million output tokens. That is a request setting, not measured consumption.
Another honeypot recorded the AutoGenX-Engine header family on model-API probes. Here, the repeated tasks show capability testing. We found no customer conversation histories, resale prices, or key-pool configuration in this source’s captures.
The recorded reverse-DNS name was sportsmanslocker.com, but forward DNS did not match the source address when checked. We have not identified the operator. Its credential did not match our publication records, so these counts are separate from the leaked-key totals.
Access checks
What happened after a key worked
The requests also showed how people tested the keys: trying different model names, repeating a check on a schedule, or using the same key from several addresses. In the examples below, we’ve replaced IPs with labels that apply only within each example. We estimated start times from completion timestamps and latency, including time spent waiting in the gateway.
Model checks
One greeting, 29 model names
One IP used a published key to send hi while requesting 29 different model names. The requests started within about 27 seconds, and all returned a successful HTTP status.
hi| Estimated time offset | Request |
|---|---|
| +0.000s | Source A · claude-fable-5-1 · HTTP 200 |
| +0.005s | Source A · gpt-4o · HTTP 200 |
| +0.007s | Source A · gpt-6-luna · HTTP 200 |
29 model checks in about 27 seconds
One IP sent the same greeting to each model name.
The line counts requests by estimated start time. It shows when the checks began, not when replies arrived.
We found 38 sessions lasting no more than ten minutes where one IP/key pair sent the same message under at least five model names.
Scheduled checks
A working key was checked roughly once an hour
In another series, one IP kept sending ping with the same key and model name. We recorded 95 successful requests across 5 observed days. The requests carried a Python urllib header.
ping| Estimated time offset | Request |
|---|---|
| +0h 00m 00s | Source A · claude-opus-5-5 · HTTP 200 |
| +1h 00m 25s | Source A · claude-opus-5-5 · HTTP 200 |
| +2h 01m 29s | Source A · claude-opus-5-5 · HTTP 200 |
The time between checks stayed close to an hour
One source IP, one published key, one model, the same ping prompt.
The intervals use estimated start times. The requests span parts of five days.
We also saw roughly five-minute checks carrying a “sub2api” header. The public Sub2API gateway includes scheduled model checks, but the header alone doesn’t tell us which deployment sent these requests.
We saw 23 IPs return on later days, out of the 80 that sent successful prompts with our published keys. Of those, 3 appeared on seven or more days.
Use from several addresses
Seven IPs used the same key within a minute
In one burst, 7 source IPs used a single published key to send 10 successful checks under 10 model names.
liveness| Estimated time offset | Request |
|---|---|
| +0.000s | Source A · gpt-5-mini · HTTP 200 |
| +0.879s | Source B · gpt-4.1 · HTTP 200 |
| +1.584s | Source C · o3 · HTTP 200 |
Every request carried a curl header and the same 8-character message: liveness. Response recording had not begun; the success statuses come from request metadata.
The IPs could belong to several users or to one script using several network exits.
The same key check appeared from eight countries
A key we had planted in payloadgrove-dev/http-header-safety-check appeared in 23 successful requests from 8 recorded source addresses. Every request used the same 24-character message and an undici header. Together, they checked 17 model names in about 30 minutes.
The matching key, message, and header suggest coordinated testing. The recorded addresses geolocate to eight countries; the logs do not separately verify the socket peers.
Scripts, tools, and timing
Recognizing scripts and tools in the request headers
We examined the 4,711 successful prompt requests using our published keys. They came from 80 distinct source IPs, using a mix of scripts, browsers, and named tools.
The user-agent header often named a library or tool. The sender can change it, so we checked the labels against request behavior and public software.
The chart shows the most common header categories, not every request. An IP can appear in several rows.
The named-tool row covers “keypool,” “Zoltraak,” and “sub2api.” We looked for public software that could explain these labels:
| Name in the header | What we could establish | What we saw in our traffic |
|---|---|---|
| Sub2API | An open-source gateway that turns access to AI subscription accounts and upstream API keys into an API for its users. It handles authentication, routing, and billing. Its monitoring code checks whether upstream models answer. | 2,421 prompt requests carried the label; 320 returned HTTP success. Some short checks were roughly five minutes apart. We haven’t identified the deployment sending them. |
| keypool | Key pooling means managing multiple API keys through one program or gateway. Public projects such as llm-keypool do this. We found no verified code match for the software sending these requests. | 1,060 labelled prompt requests; 823 returned HTTP success. Alongside short checks, requests carrying this label included the recurring collector description. |
| Zoltraak | We couldn’t identify the software behind this label. A separate gateway operator also reported a ZOLTRAAK header while investigating an exposed AI gateway. | 675 labelled prompt requests; 410 returned HTTP success. These included short checks and prompts asking for a value that changed with each request. |
Daily activity
How leaked-key use changed over time
We tracked the keys we leaked on GitHub to see how they were used. The chart shows all 11,841 requests using those keys: model-list checks, prompts, and failed attempts.
Requests using published keys
Daily counts from September 6 through the October 11 snapshot (UTC). The vertical axis uses a logarithmic scale.
Operator takeaways
What to look for in your own gateway logs
The examples give us three patterns to investigate when grouping gateway requests by credential:
- Many model names requested through the same key in a short window.
- Repeated checks at a stable interval after a key has already worked.
- A change in the addresses using a key, especially during a burst of prompt requests.
Compare these patterns with your own SDKs, load balancing, and monitoring before treating them as abuse. A stable identifier lets you group requests without storing the raw key. We haven’t measured detection accuracy or false positives.
Methods
How we counted and linked the requests
Scope. Aggregate counts use the October 11 snapshot and only keys backed by our publication records. Known owner traffic, including our Israel and Westfield addresses, is excluded. The eight-address test comes from a separate 24-hour capture ending October 12; its counts are not added to the frozen totals.
Capture. Response recording started partway through the study. HTTP success comes from request metadata and does not establish answer quality. Older captures could truncate long text.
Attribution. Headers and conversation histories are supplied by the requester. A DNS match corroborates a recorded address, not a person. Older logs did not separately retain socket peers, so forwarded addresses cannot all be independently verified.
Labels. Coding and agent categories use lexical indicators in the latest user message. Embedded instructions, tool results, and Telegram fields are submitted context, not independently authenticated activity.
Exposure. Keys were published in waves and had different amounts of time to be found. New York and San Francisco also had different observation windows and API surfaces. The totals describe this experiment, not a geographic comparison or a general leak rate.
InferTrail
Seeing similar patterns in your gateway logs?
I’m happy to compare notes on what you’re seeing and which signals are available in your gateway metadata.