
LMCache CVE-2026-105192: Unpatched 9.8 RCE
LMCache CVE-2026-105192: Unpatched 9.8 RCE
On 7 October 2026, JFrog published a vulnerability in LMCache that is hard to overstate: unauthenticated remote code execution, CVSS 9.8, and no fixed release to install. The CVE record assigned the same day carries the same score and the same empty patch column.
LMCache is the KV-cache layer people bolt onto vLLM so the same prefix does not get recomputed on every request. It is infrastructure software, which means it tends to run quietly on a GPU box somewhere and get touched only when throughput drops. That is exactly the profile of a component nobody notices until a scanner does.
The bug itself is not a surprise to anyone who has watched Python ship data over a socket. The interesting part is the shape of the exposure: your entire CVSS score comes down to one command-line flag, and the project's own example manifest sets that flag.
What actually breaks
LMCache has a multiprocess mode, also called distributed mode, where the cache runs as a standalone server that worker processes reach over ZeroMQ. That server opens a ROUTER socket so peers can register and share KV cache blocks. There is no CURVE, no ZAP, no password, and no message-level authentication on it. The intended clients are sibling LMCache processes, and nothing on the wire checks that a client actually is one.
Messages on that socket are msgpack. Extension code 1 is registered for
DeviceIPCWrapper, and it is handed to
DeviceIPCWrapper.Deserialize,
which calls pickle.loads. pickle will happily reconstruct an object that
runs code as it decodes — that is the whole design of the format.
What makes this exploitable rather than merely bad is when the unpickling
happens. The hook fires from ext_hook inside
custom_types.py
while the server is still decoding the arguments of a REGISTER_KV_CACHE
message — before the handler runs, before the server has looked at what kind of
message it even received. A single unauthenticated DEALER frame to the
transport port (default 5555) therefore executes the sender's code during
decode.
The handler then fails, because it expected a DeviceIPCWrapper and got the
return value of os.system. That error is logged after the command has already
run. The code runs with the privileges of the LMCache process, and on the
project's official container images that process runs as root, according to
JFrog's advisory.
JFrog credits Yuval Moravchick of its security research team with the find. The
CVE record classifies it as CWE-502, deserialization of untrusted
data, paired with CWE-306,
missing authentication for a critical function — which is the pairing you would
expect from a serializer and a socket that was never meant to face anyone.
Worth stating plainly: a copy of LMCache running inside a single vLLM process does not open this port at all. This is a distributed-mode bug.
Your CVSS score is a command-line flag
By default the multiprocess server binds to localhost, so another host cannot reach it. It becomes reachable when an operator starts it with a routable address — which is exactly how multi-node deployments share one cache across machines. That is the whole distance between "not exposed" and 9.8.
JFrog's CVSS entry says so in the scenario field rather than burying it: "Multiprocess mode bound to a routable address, the documented multi-node deployment." The score is not for a misconfiguration. It is for following the documentation.
And the documentation's own example agrees. LMCache's example Kubernetes manifest starts the server listening on every network interface:
# The documented multi-node setting — and the vulnerable one.
python -m lmcache.v1.multiprocess.http_server \
--http-host 127.0.0.1 --http-port 8080 \
--host 0.0.0.0 --port 5555 \
--l1-size-gb 0.25 --eviction-policy LRU \
--hash-algorithm blake3 --instance-id mp-node-01
--host 0.0.0.0 is the line that matters. Keep the transport on localhost and
a remote attacker has nothing to send a frame to. Put it behind a firewall and
you have reduced who can reach the port, but you have not removed the
problem — JFrog is explicit that any host that can still open a connection to
it can run code as the LMCache user.
There is nothing to upgrade to
This is the part that makes it worse than a routine patch advisory.
- Affected: 0.3.9 (released October 2025) through 0.5.5, the latest
stable on PyPI, plus the
0.5.6release candidates through0.5.6rc3and the development branch as of 7 October 2026. - Fixed: nothing. The decode path is still there in the latest release.
- Vendor advisory: none. LMCache's GitHub security advisories page is empty.
So there is no version number to target, which also means there is no clean
way to answer "are we patched?" The pragmatic mitigation is configuration, not
patching: do not set --host to a routable address, keep the multiprocess port
on localhost or on a trusted cluster network, and if you genuinely need
multi-node, treat every machine that can reach that port as a machine that can
execute code as your LMCache user.
The fixes JFrog describes for the underlying design are the ones you would
write yourself: stop handing network bytes to pickle, replace the serializer
behind extension code 1 with something that cannot carry code, authenticate
the ZMQ transport — CURVE, or an HMAC over each message — and refuse to bind a
routable address unless that authentication is configured.
One gap worth naming: the advisory does not give operators any way to determine whether a server has already been attacked. There is no indicator of compromise in it.
Check your exposure before you open a ticket
The script below is a static check: it reads the installed LMCache, looks for
the pickle.loads call, and reads the local TCP listener table for a routable
bind on the transport port. It sends nothing, so it is safe to run on
production. Exit code 1 means exposure, 0 means none found, 2 means it
could not tell.
#!/usr/bin/env python3
"""Static exposure check for CVE-2026-105192 (LMCache pickle RCE).
Run it with the same interpreter that runs your LMCache or vLLM stack.
It never touches the network - it only reads the local install and the
local TCP listener table, and answers three questions:
1. Is the installed LMCache in the affected range (>= 0.3.9)?
2. Does the installed code still hand socket bytes to pickle.loads?
3. Is the multiprocess ZMQ transport listening on a routable address?
Exit codes: 0 = no exposure found, 1 = exposure found, 2 = cannot tell.
"""
import re
import socket
import sys
from importlib import metadata
from pathlib import Path
AFFECTED_SINCE = (0, 3, 9)
TRANSPORT_PORT = 5555 # LMCache multiprocess ZMQ default
LOOPBACK = {"127.0.0.1", "::1"}
def parse_version(text):
m = re.match(r"(\d+)\.(\d+)\.(\d+)", text or "")
return tuple(int(g) for g in m.groups()) if m else None
def installed_version():
try:
return metadata.version("lmcache")
except metadata.PackageNotFoundError:
return None
def pickle_source_path():
try:
import lmcache
root = Path(lmcache.__file__).resolve().parent
except Exception:
return None
path = root / "v1" / "platform" / "base" / "ipc_wrapper.py"
return path if path.is_file() else None
def listening_sockets(proc_net=None):
"""Return (port, addr) for every TCP listener. Linux /proc only."""
base = Path(proc_net) if proc_net else Path("/proc/net")
found = []
for name in ("tcp", "tcp6"):
try:
lines = (base / name).read_text().splitlines()[1:]
except OSError:
continue
for line in lines:
parts = line.split()
if len(parts) < 4 or parts[3] != "0A": # 0A = TCP_LISTEN
continue
addr_hex, port_hex = parts[1].split(":")
port = int(port_hex, 16)
if name == "tcp6":
raw = bytes.fromhex(addr_hex)
# the kernel stores each 32-bit word little-endian
raw = b"".join(raw[i:i + 4][::-1] for i in range(0, 16, 4))
addr = socket.inet_ntop(socket.AF_INET6, raw)
else:
addr = socket.inet_ntoa(bytes.fromhex(addr_hex)[::-1])
found.append((port, addr))
return found
def is_routable(addr):
if addr in ("0.0.0.0", "::", "::0"):
return True
if addr in LOOPBACK:
return False
return not addr.startswith("fe80")
def main():
findings = []
exit_code = 0
version = installed_version()
if version is None:
findings.append("? lmcache is not importable here - run me with the "
"interpreter that has it installed.")
exit_code = 2
else:
parsed = parse_version(version)
if parsed and parsed >= AFFECTED_SINCE:
findings.append(f"[!] LMCache {version} is in the affected range "
"(>= 0.3.9); no fixed release exists as of "
"2026-10-07.")
exit_code = 1
else:
findings.append(f"[ok] LMCache {version} predates the vulnerable "
"code path (introduced in 0.3.9).")
src = pickle_source_path()
if src is None:
findings.append("? could not locate ipc_wrapper.py to inspect.")
exit_code = max(exit_code, 2)
elif "pickle.loads" in src.read_text(errors="ignore"):
findings.append(f"[!] {src} still calls pickle.loads on socket data.")
exit_code = max(exit_code, 1)
else:
findings.append(f"[ok] {src} no longer unpickles socket data.")
open_ports = {}
for port, addr in listening_sockets():
open_ports.setdefault(port, set()).add(addr)
addrs = open_ports.get(TRANSPORT_PORT)
if not addrs:
findings.append(f"[ok] nothing is listening on port {TRANSPORT_PORT} "
"on this host.")
elif any(is_routable(a) for a in addrs):
bad = sorted(a for a in addrs if is_routable(a))
findings.append(f"[!] ZMQ transport port {TRANSPORT_PORT} is bound to "
f"routable address {', '.join(bad)} - reachable "
"from other machines.")
exit_code = max(exit_code, 1)
else:
findings.append(f"[ok] port {TRANSPORT_PORT} is bound to loopback "
"only ({}).".format(", ".join(sorted(addrs))))
print("CVE-2026-105192 exposure check")
for line in findings:
print(" " + line)
print("=> " + ("EXPOSED" if exit_code == 1 else
"cannot tell" if exit_code == 2 else "no exposure found"))
return exit_code
if __name__ == "__main__":
sys.exit(main())
Six more reports, one accuser
The day before the CVE went public, one GitHub account filed six additional
LMCache security reports in less than four hours: unauthenticated
cross-tenant KV cache
read, write, and unbounded storage,
an unauthenticated fleet control plane on
0.0.0.0:9300, an
allocation-hold and key-injection flaw in the PD transfer backend, a
run_script sandbox escape, an
unauthenticated cache control plane and environment
disclosure, and proxy
catalogue poisoning.
Treat all six carefully. They come from one account, they rest on
proof-of-concept claims, and they carry no CVE, no confirmation from the
maintainers, and no fix — the only replies so far are other users volunteering
to take them on. One does point at a default that has since moved on its own:
the multiprocess HTTP server bound to 0.0.0.0:8080 in 0.5.5 binds to
127.0.0.1 in the 0.5.6 release candidates. The issues were all still open as
of this writing. Reporting them is fine; treating them as confirmed bugs would
be a different claim than the evidence supports.
Adjacent, not the same bug
Do not confuse this with CVE-2026-105756,
the vLLM denial-of-service fixed in vLLM 0.30.0, released 22 September 2026.
That one
was a malformed cache_salt value crashing the engine on deployments using the
LMCache multiprocess connector, rated 6.5, with no code execution. It is fixed.
CVE-2026-105192 is not.
The pattern this belongs to
Handing data from an unauthenticated network socket to pickle is not a new
mistake. Researchers documented the same core flaw across a group of AI
inference frameworks in November 2025 and called the collection
ShadowMQ.
Whether LMCache's code shares a common source with those projects has not been
established, and it would be sloppy to imply otherwise.
What is established is that the inference stack keeps rediscovering the same lesson: an internal service account, a port that defaults to reachable in the examples people copy, and a serialization format that executes code. The JFrog Artifactory chain we covered in September was a different bug with a different shape, but the same operational question afterward — what is actually listening, and who can reach it. It is the same question MCP deployments keep failing, one public registry at a time.
What to do today
- Find out whether you run LMCache in multiprocess mode. If your cache runs inside a single vLLM process, this port does not exist for you.
- If you do, check the bind address before anything else. Loopback-only is
not remotely exploitable;
0.0.0.0is. - If you need multi-node, put the transport on a trusted cluster network and firewall it, then assume every host on that network can execute code as the LMCache user — because they can.
- Do not wait for a patch that has a schedule. None has been published, and the vendor has not issued an advisory.
- Read the JFrog advisory yourself — it carries the working proof of concept and the full mitigation list.
An unpatched 9.8 on a component that exists to make inference cheaper is not a niche finding. The fix will eventually be a serializer swap and an authenticated socket. Until it ships, the security boundary is one flag wide, and the example config is on the wrong side of it.
Sources
- JFrog Security Research: LMCache is vulnerable to Unauthenticated Remote Code Execution via Pickle Deserialization on the Multiprocess ZMQ Transport
- CVE Program: CVE-2026-105192
- The Hacker News: Unpatched Critical LMCache Flaw Lets Unauthenticated Attackers Run Code Remotely
- vLLM Project: GHSA-2823-qmq8-rwvj: vLLM cache_salt denial of service
- LMCache (GitHub): Unauthenticated cross-tenant KV cache read, write, and unbounded storage on LMCache server
// author
Editor & Operator
Mohd Firdaus Abu Bakar (MrDodgerX) is an enterprise geospatial systems engineer in Malaysia and the editor behind hejes.my. He runs the briefing pipeline, curates and edits every draft, verifies each factual claim against primary sources, and presses the publish button — nothing ships without his review. He writes about AI, security and Malaysia's tech scene from a builder's perspective. Professional profile and code: the links below.
// sources //
fact-check verified · 08 OCT 2026 · hejes-content-fact-check
- LMCache is vulnerable to Unauthenticated Remote Code Execution via Pickle Deserialization on the Multiprocess ZMQ TransportJFrog Security Research · accessed 08 OCT 2026
- CVE-2026-105192CVE Program · accessed 08 OCT 2026
- Unpatched Critical LMCache Flaw Lets Unauthenticated Attackers Run Code RemotelyThe Hacker News · accessed 08 OCT 2026
- GHSA-2823-qmq8-rwvj: vLLM cache_salt denial of servicevLLM Project · accessed 08 OCT 2026
- Unauthenticated cross-tenant KV cache read, write, and unbounded storage on LMCache serverLMCache (GitHub) · accessed 08 OCT 2026
related //

FortiMail Zero-Day: Disable IBE and Hunt
A 9.8 path traversal plus NULL byte flaw in FortiMail is under attack. The workaround is three CLI lines, and it does nothing for boxes already written to.

Flock's CEO says just gunshots. Its website says more.
Flock's CEO told Disrupt its audio sensors detect "just gunshots", but the company's own product page and FAQ list crashes, fireworks, and sideshows too.

Anthropic finds ingredients for future AI worms
Anthropic tests show small open-weight models can assemble replication steps, but hardware and bandwidth limits make AI worms slow for now.
// join the feed
one fresh insight per week. no spam, ever.
weekly digest. unsubscribe anytime.