---
id: "GHSA-894j-wg8m-63h8"
page: "https://infrastack.ai/live/GHSA-894j-wg8m-63h8"
kind: "vulnerability"
aliases: ["GHSA-894j-wg8m-63h8"]
title: "vLLM OpenCVDynamicOpenPanguVideoBackend: request-level num_frames without a ceiling \u2014 unauthenticated memory/CPU exhaustion (OOM-kill) on any video-capable deployment"
severity: "medium"
cvss: 5.3
epss: null
exploited: false
status: "open"
category: "ai-tooling"
products: [{"name": "vllm", "fixed": ["0.31.0"], "affected": ">= 0.30.0, < 0.31.0", "ecosystem": "pypi"}]
ecosystems: ["pypi"]
tags: ["ai-tooling", "cwe-400", "cwe-770", "multimodal", "openai-compatible-api", "self-hosted", "vllm"]
not_affected_if: ["on-vllm-0.31.0-or-later"]
published_at: "2026-10-27T22:24:06+00:00"
first_seen_at: "2026-11-09T22:53:23+00:00"
updated_at: "2026-11-09T22:56:47+00:00"
sources: [{"source": "ghsa-repo", "url": "https://github.com/vllm-project/vllm/security/advisories/GHSA-894j-wg8m-63h8", "first_seen_at": "2026-11-09T22:53:23+00:00"}]
exploit_sources: []
---

# vLLM OpenCVDynamicOpenPanguVideoBackend: request-level num_frames without a ceiling — unauthenticated memory/CPU exhaustion (OOM-kill) on any video-capable deployment

Link for people: https://infrastack.ai/live/GHSA-894j-wg8m-63h8 (this .md is for agents)

vLLM's OpenCV video backend uses a request-supplied num_frames value with no upper limit, so the frame-index allocation grows with whatever a client sends. An unauthenticated request to the chat completions endpoint that selects this backend can exhaust memory and CPU in the API server process until it is killed.

_Summary, check and fix written by a model from the sources; labelled as such._

## Affected

- pypi:vllm affected >= 0.30.0, < 0.31.0 fixed 0.31.0

## How to check

- Check the installed version with `pip show vllm`; versions `>= 0.30.0` and `< 0.31.0` are affected.
- Check whether the server was launched with `--trust-request-mm-kwargs`; the advisory says to keep it disabled for untrusted clients.
- Check whether the deployment accepts video input at all, since any video-capable deployment is reachable.

## Fix

- vLLM: move to `0.31.0` or later.
- Keep `--trust-request-mm-kwargs` disabled for untrusted clients.

## Timeline

- 2026-11-09T22:53:23+00:00 ghsa-repo https://github.com/vllm-project/vllm/security/advisories/GHSA-894j-wg8m-63h8

## Documents

- [advisory] Maintainer's advisory: github.com https://github.com/vllm-project/vllm/security/advisories/GHSA-894j-wg8m-63h8

## From the sources

## Summary

`OpenCVDynamicOpenPanguVideoBackend` (`vllm/multimodal/video.py:1222-1260`) drives its frame-index computation from the request-controlled `num_frames` with **no ceiling**. The backend is registered without a `video_processor` binding, so no model selects it by default — but `VideoMediaIO` honors request-level `media_io_kwargs.video.video_backend` for any registered **CPU** loader (`merge_kwargs`, `vllm/multimodal/media/video.py:42-90`, strips only `hw_decoders`/`pool_size`/`device`/unconfigured-GPU backends and clamps none of the sampling knobs). One unauthenticated `POST /v1/chat/completions` (or `/tokenize`) with `"video_backend": "openpangu"` routes any video through this sampler on **any video-capable deployment** — no GLM/OpenPangu model required, no non-default flags, no auth.

## Root cause

```python
num_frames = target.num_frames          # request-controlled, unclamped
fps = target.fps                        # request-controlled
if fps > 0:
    if num_frames >= int(total_duration * fps) + 1:
        num_frames = int(total_duration * fps) + 1   # clamp only fires when ABOVE duration*fps
...
sample_frame_timestamps = np.linspace(0, total_duration, num_frames, dtype=float)
frames_indices = [min(total_frames_num - 1, round(t * original_fps))
                  for t in sample_frame_timestamps]
```

The only clamp fires when `num_frames >= int(duration*fps)+1`. Choosing `num_frames` just below that bound — or `fps=-1`, which skips the branch entirely — leaves the request value untouched: `np.linspace(0, d, num_frames)` allocates `8 × num_frames` bytes and the Python comprehension walks every element, inside the API-server process.

Measured in-process (official CPU image, `vllm/vllm-openai-cpu:latest` v0.30.0):

- `num_frames=1e7` → 462 MB peak, 118 s CPU
- `num_frames=1e9` → ~46 GB / hours (linear extrapolation) → OOM-kill

This is an instance of the published unclamped-`num_frames` family (GHSA-vxqj-p4gw-9h4c; central clamp PR #51969 subsequently superseded by #58830), with two aggravating differences: the sink is a distinct `np.linspace` + list-comprehension allocation, and the backend is reachable via request-level backend selection **without any model opting into it**.

## Suggested fix

- Backend: clamp `num_frames` before `np.linspace` (e.g. `min(num_frames, total_frames_num)` or a class-level cap) and treat `fps=-1` the same.
- Central: land the `num_frames` clamp in `VideoMediaIO.merge_kwargs` (where PR #51969 is adding it) so per-backend audits stop missing siblings, and consider gating request-level `video_backend` selection to an allowlist.

## Static reach

`vllm/multimodal/media/video.py:42-90` (`merge_kwargs`: no clamp on `num_frames`/`fps`; GPU-only gating for `video_backend`); sink at `vllm/multimodal/video.py:1222-1260`.

## Affected versions

- Package: `vllm` (PyPI).
- Affected versions: `>= 0.30.0, < 0.31.0`.
- First stable patched version: `0.31.0`.

PR #58830 blocks untrusted per-request `mm_processor_kwargs` and `media_io_kwargs` by default before preprocessing. Keep `--trust-request-mm-kwargs` disabled for untrusted clients. Explicitly enabling it restores trusted override behavior; the underlying processor/sampler is not independently bounded by this fix.

The lower bound records the verified affected v0.30.0 release. The backend predates that release; earlier request-path exposure has not been comprehensively assessed.

Advisory fixed in [#58830](https://github.com/vllm-project/vllm/pull/58830).

---

_Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai_
