CVE-2026-34755: vLLM: Denial of Service via Unbounded Frame Count in video/jpeg Base64 Processing
Summary The VideoMediaIO.loadbase64() method at vllm/multimodal/media/video.py:51-62 splits video/jpeg data URLs by comma to extract individual JPEG frames, but does not enforce a frame count limit. The numframes parameter (default: 32), which is enforced by the loadbytes() code path at line 47-48, is completely bypassed in the video/jpeg base64 path.
Published Apr 3, 2026 · Updated Aug 25, 2026
Affected packages
vllm
- Ecosystem
- PyPI
- Affected
- >= 0.7.0, < 0.19.0
- Fixed in
- 0.19.0
- Check
pip show vllm
Advisory details
From the GitHub Advisory Database (CC BY 4.0).
Summary
The VideoMediaIO.load_base64() method at vllm/multimodal/media/video.py:51-62 splits video/jpeg data URLs by comma to extract individual JPEG frames, but does not enforce a frame count limit. The num_frames parameter (default: 32), which is enforced by the load_bytes() code path at line 47-48, is completely bypassed in the video/jpeg base64 path. An attacker can send a single API request containing thousands of comma-separated base64-encoded JPEG frames, causing the server to decode all frames into memory and crash with OOM.
Details
Vulnerable code
# video.py:51-62
def load_base64(self, media_type: str, data: str) -> tuple[npt.NDArray, dict[str, Any]]:
if media_type.lower() == "video/jpeg":
load_frame = partial(self.image_io.load_base64, "image/jpeg")
return np.stack(
[np.asarray(load_frame(frame_data)) for frame_data in data.split(",")]
# ^^^^^^^^^^
# Unbounded split — no frame count limit
), {}
return self.load_bytes(base64.b64decode(data))The load_bytes() path (line 47-48) properly delegates to a video loader that respects self.num_frames (default 32). The load_base64("video/jpeg", ...) path bypasses this limit entirely — data.split(",") produces an unbounded list and every frame is decoded into a numpy array.
video/jpeg is part of vLLM's public API
video/jpeg is a vLLM-specific MIME type, not IANA-registered. However it is part of the public API surface:
encode_video_url()atvllm/multimodal/utils.py:96-108generatesdata:video/jpeg;base64,...URLs- Official test suites at
tests/entrypoints/openai/test_video.py:62andtests/entrypoints/test_chat_utils.py:153both use this format
Memory amplification
Each JPEG frame decodes to a full numpy array. For 640x480 RGB images, each frame is ~921 KB decoded. 5000 frames = ~4.6 GB. np.stack() then creates an additional copy. The compressed JPEG payload is small (~100 KB for 5000 frames) but decompresses to gigabytes.
Data flow
POST /v1/chat/completions
→ chat_utils.py:1434 video_url type → mm_parser.parse_video()
→ chat_utils.py:872 parse_video() → self._connector.fetch_video()
→ connector.py:295 fetch_video() → load_from_url(url, self.video_io)
→ connector.py:91 _load_data_url(): url_spec.path.split(",", 1)
→ media_type = "video/jpeg"
→ data = "<frame1>,<frame2>,...,<frame10000>"
→ connector.py:100 media_io.load_base64("video/jpeg", data)
→ video.py:54 data.split(",") ← UNBOUNDED
→ video.py:55-57 all frames decoded into numpy arrays
→ video.py:56 np.stack([...]) ← massive combined array → OOMconnector.py:91 uses split(",", 1) which splits on only the first comma. All remaining commas stay in data and are later split by video.py:54.
Comparison with existing protections
| Code Path | Frame Limit | File |
| `load_bytes()` (binary video) | Yes — `num_frames` (default 32) | video.py:46-49 |
| `load_base64("video/jpeg", ...)` | No — unlimited `data.split(",")` | video.py:51-62 |Credited to SEORY0, russellb, jperezdealgaba, DarkLight1337, Isotr0py.
References
- https://github.com/vllm-project/vllm/security/advisories/GHSA-pq5c-rjhq-qp7p
- https://github.com/vllm-project/vllm/pull/38636
- https://github.com/vllm-project/vllm/commit/58ee61422169ce17e08248f8efa1e9df434fe395
- https://nvd.nist.gov/vuln/detail/CVE-2026-34755
- https://github.com/pypa/advisory-database/tree/main/vulns/vllm/PYSEC-2026-144.yaml
- https://access.redhat.com/errata/RHSA-2026:36005
- https://access.redhat.com/errata/RHSA-2026:36006
- https://access.redhat.com/security/cve/CVE-2026-34755
- https://bugzilla.redhat.com/show_bug.cgi?id=2455403
- https://security.access.redhat.com/data/csaf/v2/vex/2026/cve-2026-34755.json
- https://access.redhat.com/errata/RHSA-2026:57380
- https://access.redhat.com/errata/RHSA-2026:57390
- https://access.redhat.com/errata/RHSA-2026:57387
- https://access.redhat.com/errata/RHSA-2026:57389
- https://access.redhat.com/errata/RHSA-2026:59144
- https://access.redhat.com/errata/RHSA-2026:59151
- https://github.com/advisories/GHSA-pq5c-rjhq-qp7p
How this page is made: the data panel comes from the GitHub Advisory Database, CISA’s KEV catalog, the NVD and FIRST EPSS, and updates twice a day. No analysis has been written for this entry yet. Methodology
More vLLM vulnerabilities
All vLLM entriesCVE-2026-105752
vLLM: Harmony tool continuations drop `cache_salt` — restoring a cross-tenant prefix-cache membership oracle
low3.1Oct 6, 2026CVE-2026-105753
vLLM: Mirrored multimodal IPC caches desync after a rejected request — a later request reusing the same media hash trips a receiver assertion in the engine core
medium6.5Oct 6, 2026CVE-2026-105754
vLLM: Scale-out disaggregated multimodal transport trusts caller-supplied features
medium6.5Oct 5, 2026CVE-2026-105755
vLLM: Flash late-interaction scoring caches query embeddings under a caller-controlled request id — cross-request integrity break and induced errors on `/score` and `/rerank`
medium4.2Oct 5, 2026CVE-2026-105756
vLLM: Loose `cache_salt` validation lets a single request kill EngineCore on LMCache-MP deployments — uncaught downstream `ValueError` denial of service
medium6.5Oct 5, 2026CVE-2026-105757
vLLM: Structured-output request errors escape the request boundary and terminate the shared EngineCore — engine-fatal denial of service (3 sites)
medium6.5Oct 5, 2026
