{"id":"CVE-2026-34755","aliases":["EUVD-2026-19350","GHSA-pq5c-rjhq-qp7p","PYSEC-2026-144"],"status":"published","title":"vLLM Affected by Denial of Service via Unbounded Frame Count in video/jpeg Base64 Processing","description":"vLLM is an inference and serving engine for large language models (LLMs). From 0.7.0 to before 0.19.0, the VideoMediaIO.load_base64() method at vllm/multimodal/media/video.py 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, 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. This vulnerability is fixed in 0.19.0.","severity":{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-10","score":0.00482,"percentile":0.41896,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-770"],"published":"2026-04-03T21:51:35Z","modified":"2026-10-28T02:30:44Z","fixes":[{"ecosystem":"PyPI","package":"vllm","fixed_in":["0.19.0"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"PyPI","package":"vllm","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0.7.0"},{"fixed":"0.19.0"}]}]},{"source":"osv","ecosystem":"PyPI","package":"vllm","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0.7.0"},{"fixed":"0.19.0"}]}],"versions":["0.10.0","0.10.1","0.10.1.1","0.10.2","0.11.0","0.11.1","0.11.2","0.12.0","0.13.0","0.14.0","0.14.1","0.15.0","0.15.1","0.16.0","0.17.0","0.17.1","0.18.0","0.18.1","0.7.0","0.7.1","0.7.2","0.7.3","0.8.0","0.8.1","0.8.2","0.8.3","0.8.4","0.8.5","0.8.5.post1","0.9.0","0.9.0.1","0.9.1","0.9.2"]},{"source":"osv","ecosystem":"PyPI","package":"vllm","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0.7.0"},{"fixed":"0.19.0"}]}],"versions":["0.10.0","0.10.1","0.10.1.1","0.10.2","0.11.0","0.11.1","0.11.2","0.12.0","0.13.0","0.14.0","0.14.1","0.15.0","0.15.1","0.16.0","0.17.0","0.17.1","0.18.0","0.18.1","0.7.0","0.7.1","0.7.2","0.7.3","0.8.0","0.8.1","0.8.2","0.8.3","0.8.4","0.8.5","0.8.5.post1","0.9.0","0.9.0.1","0.9.1","0.9.2"]}],"products":[{"vendor":"red hat","product":"red hat ai inference server"},{"vendor":"red hat","product":"red hat ai inference server 3.2"},{"vendor":"red hat","product":"red hat ai inference server 3.4"},{"vendor":"red hat","product":"red hat enterprise linux ai 3.4"},{"vendor":"red hat","product":"red hat openshift ai (rhoai)"},{"vendor":"vllm","product":"vllm"},{"vendor":"vllm-project","product":"vllm"}],"references":[{"url":"https://github.com/vllm-project/vllm/security/advisories/GHSA-pq5c-rjhq-qp7p","tags":["x_refsource_CONFIRM","Patch","Vendor Advisory","advisory","web","fix"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-34755","tags":["vdb-entry","x_refsource_REDHAT","advisory","web"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2455403","tags":["issue-tracking","x_refsource_REDHAT","report","web"]},{"url":"https://security.access.redhat.com/data/csaf/v2/vex/2026/cve-2026-34755.json","tags":["x_sadp-csaf-vex","web"]},{"url":"https://access.redhat.com/errata/RHSA-2026:36005","tags":["vendor-advisory","x_refsource_REDHAT","advisory","web"]},{"url":"https://access.redhat.com/errata/RHSA-2026:36006","tags":["vendor-advisory","x_refsource_REDHAT","advisory","web"]},{"url":"https://access.redhat.com/errata/RHSA-2026:57380","tags":["vendor-advisory","x_refsource_REDHAT","advisory","web"]},{"url":"https://access.redhat.com/errata/RHSA-2026:57389","tags":["vendor-advisory","x_refsource_REDHAT","advisory","web"]},{"url":"https://access.redhat.com/errata/RHSA-2026:57390","tags":["vendor-advisory","x_refsource_REDHAT","advisory","web"]},{"url":"https://access.redhat.com/errata/RHSA-2026:57387","tags":["vendor-advisory","x_refsource_REDHAT","advisory","web"]},{"url":"https://access.redhat.com/errata/RHSA-2026:59151","tags":["vendor-advisory","x_refsource_REDHAT","advisory","web"]},{"url":"https://access.redhat.com/errata/RHSA-2026:59144","tags":["vendor-advisory","x_refsource_REDHAT","advisory","web"]},{"url":"https://github.com/vllm-project/vllm/pull/38636","tags":["web"]},{"url":"https://github.com/vllm-project/vllm/commit/58ee61422169ce17e08248f8efa1e9df434fe395","tags":["web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-34755","tags":["advisory"]},{"url":"https://github.com/pypa/advisory-database/tree/main/vulns/vllm/PYSEC-2026-144.yaml","tags":["web"]},{"url":"https://github.com/advisories/GHSA-pq5c-rjhq-qp7p"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/34xxx/CVE-2026-34755.json","tags":["advisory"]},{"url":"https://github.com/vllm-project/vllm","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-34755","title":"vLLM Affected by Denial of Service via Unbounded Frame Count in video/jpeg Base64 Processing","description":"vLLM is an inference and serving engine for large language models (LLMs). From 0.7.0 to before 0.19.0, the VideoMediaIO.load_base64() method at vllm/multimodal/media/video.py 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, 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. This vulnerability is fixed in 0.19.0.","status":"published","published":"2026-04-06T15:38:53.201Z","modified":"2026-08-25T12:05:44.994Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H","level":"medium","assessor":"cna"},{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H","level":"medium","assessor":"adp"}],"cwes":["CWE-770","CWE-770"],"updated_at":"2026-11-02T14:20:14.302133Z"}],"euvd":[{"id":"EUVD-2026-19350","description":"vLLM is an inference and serving engine for large language models (LLMs). From 0.7.0 to before 0.19.0, the VideoMediaIO.load_base64() method at vllm/multimodal/media/video.py 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, 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. This vulnerability is fixed in 0.19.0.","status":"published","published":"2026-04-06T15:38:53Z","modified":"2026-08-25T12:05:44Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H","level":"medium"}],"updated_at":"2026-11-02T18:01:33.385611Z"}],"ghsa":[{"id":"GHSA-pq5c-rjhq-qp7p","title":"vLLM: Denial of Service via Unbounded Frame Count in video/jpeg Base64 Processing","description":"## Summary\n\nThe `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.\n\n## Details\n\n### Vulnerable code\n\n```python\n# video.py:51-62\ndef load_base64(self, media_type: str, data: str) -\u003e tuple[npt.NDArray, dict[str, Any]]:\n    if media_type.lower() == \"video/jpeg\":\n        load_frame = partial(self.image_io.load_base64, \"image/jpeg\")\n        return np.stack(\n            [np.asarray(load_frame(frame_data)) for frame_data in data.split(\",\")]\n            #                                                       ^^^^^^^^^^\n            # Unbounded split — no frame count limit\n        ), {}\n    return self.load_bytes(base64.b64decode(data))\n```\n\nThe `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.\n\n### video/jpeg is part of vLLM's public API\n\n`video/jpeg` is a vLLM-specific MIME type, not IANA-registered. However it is part of the public API surface:\n\n- `encode_video_url()` at `vllm/multimodal/utils.py:96-108` generates `data:video/jpeg;base64,...` URLs\n- Official test suites at `tests/entrypoints/openai/test_video.py:62` and `tests/entrypoints/test_chat_utils.py:153` both use this format\n\n### Memory amplification\n\nEach 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.\n\n### Data flow\n\n```\nPOST /v1/chat/completions\n  → chat_utils.py:1434   video_url type → mm_parser.parse_video()\n  → chat_utils.py:872    parse_video() → self._connector.fetch_video()\n  → connector.py:295     fetch_video() → load_from_url(url, self.video_io)\n  → connector.py:91      _load_data_url(): url_spec.path.split(\",\", 1)\n                          → media_type = \"video/jpeg\"\n                          → data = \"\u003cframe1\u003e,\u003cframe2\u003e,...,\u003cframe10000\u003e\"\n  → connector.py:100     media_io.load_base64(\"video/jpeg\", data)\n  → video.py:54          data.split(\",\")  ← UNBOUNDED\n  → video.py:55-57       all frames decoded into numpy arrays\n  → video.py:56          np.stack([...])  ← massive combined array → OOM\n```\n\n`connector.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`.\n\n### Comparison with existing protections\n\n| Code Path | Frame Limit | File |\n|-----------|-------------|------|\n| `load_bytes()` (binary video) | Yes — `num_frames` (default 32) | video.py:46-49 |\n| `load_base64(\"video/jpeg\", ...)` | No — unlimited `data.split(\",\")` | video.py:51-62 |","status":"published","published":"2026-04-03T21:51:35Z","modified":"2026-08-25T15:33:11Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H","level":"medium"}],"cwes":["CWE-770"],"updated_at":"2026-11-02T14:41:05.994358Z"}],"nvd":[{"id":"CVE-2026-34755","description":"vLLM is an inference and serving engine for large language models (LLMs). From 0.7.0 to before 0.19.0, the VideoMediaIO.load_base64() method at vllm/multimodal/media/video.py 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, 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. This vulnerability is fixed in 0.19.0.","status":"published","published":"2026-04-06T16:16:36.463Z","modified":"2026-08-25T13:18:39.24Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H","level":"medium","assessor":"secondary"},{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H","level":"medium","assessor":"secondary"}],"cwes":["CWE-770"],"updated_at":"2026-11-02T14:12:13.1424Z"}],"osv":[{"id":"CVE-2026-34755","title":"vLLM Affected by Denial of Service via Unbounded Frame Count in video/jpeg Base64 Processing","description":"vLLM is an inference and serving engine for large language models (LLMs). From 0.7.0 to before 0.19.0, the VideoMediaIO.load_base64() method at vllm/multimodal/media/video.py 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, 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. This vulnerability is fixed in 0.19.0.","status":"published","published":"2026-04-06T15:38:53.201Z","modified":"2026-10-28T02:30:44.886076878Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H","level":"medium"}],"cwes":["CWE-770"],"updated_at":"2026-11-02T15:45:02.247031Z"},{"id":"GHSA-pq5c-rjhq-qp7p","title":"vLLM: Denial of Service via Unbounded Frame Count in video/jpeg Base64 Processing","description":"## Summary\n\nThe `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.\n\n## Details\n\n### Vulnerable code\n\n```python\n# video.py:51-62\ndef load_base64(self, media_type: str, data: str) -\u003e tuple[npt.NDArray, dict[str, Any]]:\n    if media_type.lower() == \"video/jpeg\":\n        load_frame = partial(self.image_io.load_base64, \"image/jpeg\")\n        return np.stack(\n            [np.asarray(load_frame(frame_data)) for frame_data in data.split(\",\")]\n            #                                                       ^^^^^^^^^^\n            # Unbounded split — no frame count limit\n        ), {}\n    return self.load_bytes(base64.b64decode(data))\n```\n\nThe `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.\n\n### video/jpeg is part of vLLM's public API\n\n`video/jpeg` is a vLLM-specific MIME type, not IANA-registered. However it is part of the public API surface:\n\n- `encode_video_url()` at `vllm/multimodal/utils.py:96-108` generates `data:video/jpeg;base64,...` URLs\n- Official test suites at `tests/entrypoints/openai/test_video.py:62` and `tests/entrypoints/test_chat_utils.py:153` both use this format\n\n### Memory amplification\n\nEach 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.\n\n### Data flow\n\n```\nPOST /v1/chat/completions\n  → chat_utils.py:1434   video_url type → mm_parser.parse_video()\n  → chat_utils.py:872    parse_video() → self._connector.fetch_video()\n  → connector.py:295     fetch_video() → load_from_url(url, self.video_io)\n  → connector.py:91      _load_data_url(): url_spec.path.split(\",\", 1)\n                          → media_type = \"video/jpeg\"\n                          → data = \"\u003cframe1\u003e,\u003cframe2\u003e,...,\u003cframe10000\u003e\"\n  → connector.py:100     media_io.load_base64(\"video/jpeg\", data)\n  → video.py:54          data.split(\",\")  ← UNBOUNDED\n  → video.py:55-57       all frames decoded into numpy arrays\n  → video.py:56          np.stack([...])  ← massive combined array → OOM\n```\n\n`connector.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`.\n\n### Comparison with existing protections\n\n| Code Path | Frame Limit | File |\n|-----------|-------------|------|\n| `load_bytes()` (binary video) | Yes — `num_frames` (default 32) | video.py:46-49 |\n| `load_base64(\"video/jpeg\", ...)` | No — unlimited `data.split(\",\")` | video.py:51-62 |","status":"published","published":"2026-04-03T21:51:35Z","modified":"2026-09-10T03:51:02.464620651Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H","level":"medium"}],"cwes":["CWE-770"],"updated_at":"2026-11-02T15:46:53.700556Z"},{"id":"PYSEC-2026-144","description":"vLLM is an inference and serving engine for large language models (LLMs). From 0.7.0 to before 0.19.0, the VideoMediaIO.load_base64() method at vllm/multimodal/media/video.py 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, 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. This vulnerability is fixed in 0.19.0.","status":"published","published":"2026-04-06T16:16:36.463Z","modified":"2026-05-20T09:19:21.539785Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H","level":"medium"}],"updated_at":"2026-11-02T15:52:54.622769Z"}]}}
