GHSA-4hhp-h66f-j5j7 CVE-2026-73560 PYSEC-2026-3934
vLLM: SSRF + arbitrary local file read in MiMoV2OmniMultiModalProcessor `_fetch_image` and audio loader bypass MediaConnector protections
- Severity
- 6.5 / 10
- Medium · CVSS 3.1
- Exploit chance (EPSS)
- 0.41%
- Chance of exploitation in the next 30 days. Higher than 35.04% of scored vulnerabilities.
- Known exploited
- No
- Not in CISA's catalog
- Weakness
- CWE-918 Server-Side Request Forgery (SSRF)
- Fix
0.26.0- Upgrade vllm to this version or later
Description
Summary
vllm/transformers_utils/processors/mimo_v2_omni.py — the multimodal processor for MiMoV2OmniForCausalLM — issues requests.get(...) directly on user-supplied image and audio URL strings and Image.open(...) on user-supplied local paths, without the SSRF / allowed_local_media_path checks that vllm.multimodal.utils.MediaConnector was hardened with in GHSA-qh4c-xf7m-gxfc, GHSA-v359-jj2v-j536, and GHSA-pf3h-qjgv-vcpr.
This is the same bug class as those three published advisories, in a code path the patches missed. When a user passes a URL or local-file string through multi_modal_data (e.g. LLM.generate(multi_modal_data={"image": "http://..."})), the processor takes the unsanitized string and dispatches it without any URL-scheme allowlist, network-target allowlist, size cap, or local-path allowlist.
Details
File: vllm/transformers_utils/processors/mimo_v2_omni.py (current main)
Sink 1 — image SSRF + local-file read (_fetch_image, lines 231–249):
def _fetch_image(src: Any) -> Image.Image:
if isinstance(src, Image.Image):
return _to_rgb(src)
if isinstance(src, bytes):
return _to_rgb(copy.deepcopy(Image.open(BytesIO(src))))
if isinstance(src, str):
if src.startswith(("http://", "https://")):
r = requests.get(src, timeout=30) # SSRF: no allowlist, follows redirects
r.raise_for_status()
return _to_rgb(copy.deepcopy(Image.open(BytesIO(r.content))))
if src.startswith("file://"):
return _to_rgb(Image.open(src[7:])) # arbitrary local file read
if src.startswith("data:image"):
...
return _to_rgb(Image.open(src)) # fallback also opens local files
raise ValueError(f"Unrecognized image source: {type(src)}")
Sink 2 — audio SSRF (around line 471):
elif audio.startswith(("http://", "https://")):
r = requests.get(audio, timeout=30) # SSRF: same pattern
r.raise_for_status()
file_obj = io.BytesIO(r.content)
Reachability. _fetch_image is invoked from MiMoVLProcessor.process_image:
def process_image(self, image: ImageInput) -> torch.Tensor:
kw = self._resolve_img_kw(image)
src = image.image
if isinstance(src, (str, bytes)):
src = _fetch_image(src)
...
MiMoVLProcessor is wrapped by MiMoV2OmniMultiModalProcessor and registered for the MiMoV2OmniForCausalLM model architecture (vllm/model_executor/models/mimo_v2_omni.py:1169). Whenever a user passes a string into multi_modal_data["image"] (or ["audio"]) for this model, the unsanitized URL/path reaches the sink.
Comparison to the recent fixes. The remediation pattern adopted in the three earlier advisories was to route every external resource fetch through MediaConnector, which checks allowed_local_media_path and applies SSRF protection before issuing the network request. chat_utils.py (lines 838, 902, 924, 963, 1053, 1081) already uses self._connector.fetch_image / fetch_audio / fetch_video. The model processor in mimo_v2_omni.py was added later and skipped the connector — it calls requests.get and Image.open directly. Result: the public OpenAI chat-completion path is protected, but library use (LLM.generate(multi_modal_data=...)), batch processing, and any other path that lets a string reach the processor receive no protection.
Impact
- SSRF — internal-network probing / cloud-metadata theft. Standard
requests.getfollows redirects and accepts any URL. An attacker who controls amulti_modal_datavalue can:- read AWS / GCP / Azure instance metadata (e.g.
http://169.254.169.254/latest/meta-data/iam/security-credentials/), - probe internal services on the vLLM host (
http://127.0.0.1:<port>,http://10.x.y.z), - exfiltrate via DNS / HTTP timing oracles even when the body is rejected by
Image.open.
- read AWS / GCP / Azure instance metadata (e.g.
- Arbitrary local file read via
file://path(line 242) and the unguarded fallbackImage.open(src)(line 248). Any file readable by the vLLM process is reachable through the model pipeline; with suitable formats this exposes/etc/passwd,~/.aws/credentials, etc. - Server-side traffic generation / amplification by hammering arbitrary URLs from the vLLM host, with a 30-second timeout per request.
Suggested remediation
Replace direct requests.get and bare Image.open paths with MediaConnector.fetch_image / fetch_audio_async (or pass the inputs through MediaConnector before they reach the processor):
# vllm/transformers_utils/processors/mimo_v2_omni.py
from vllm.multimodal.utils import MediaConnector
_connector = MediaConnector()
def _fetch_image(src):
if isinstance(src, Image.Image):
return _to_rgb(src)
if isinstance(src, bytes):
return _to_rgb(copy.deepcopy(Image.open(BytesIO(src))))
if isinstance(src, str):
return _to_rgb(_connector.fetch_image(src)) # delegates to the hardened path
raise ValueError(f"Unrecognized image source: {type(src)}")
Same change for the audio loader at line 471. This re-uses the SSRF allowlist, allowed_local_media_path policy, and size caps that the previous patches added.
Alternative: forbid str src from reaching the processor and require all multi-modal pre-processing to go through chat_utils.py / MediaConnector before hitting the model. Larger surface change, but completes the architectural fix.
Discovery
Static review on vllm@main (HEAD as of 2026-04-30) — found by triaging the file list against the three recent SSRF advisories: the mimo_v2_omni.py processor, added after those fixes, reintroduced the same bypass class.
Reporter
Ievgen Bondarenko — sactransport2000@gmail.com — GitHub @ibondarenko1
Affected versions
PyPI
| Package | Vulnerable | Fixed in |
|---|---|---|
| vllm | < 0.26.0 |
0.26.0 |
How to fix
Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.
vllm PyPI → 0.26.0
- pip
pip install --upgrade "vllm>=0.26.0"- poetry
poetry add "vllm>=0.26.0"- uv
uv add "vllm>=0.26.0"
Severity breakdown
CVSS 3.1 6.5
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
- Attack vector
- Network
- Attack complexity
- Low
- Privileges required
- Low
- User interaction
- None
- Scope
- Unchanged
- Confidentiality
- High
- Integrity
- None
- Availability
- None
Weakness
CWE-918: Server-Side Request Forgery (SSRF)
The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.
References
Advisories
Packages
Other links
Timeline
- Published
- Last updated by the source
- Latest EPSS score
Related advisories
| Advisory | Severity | Why it's related |
|---|---|---|
| GHSA-8397-8qf6-f2gm vLLM: Phi-4 multimodal audio encoder materializes and retains full attention matrices: a single max-length audio OOM-crashes the vLLM engine | Medium | Also affects vllm |
| GHSA-x567-49g5-p4cf vLLM: Responses API bypasses trust_request_chat_template for chat_template_kwargs | Medium | Also affects vllm |
| GHSA-vvpg-rrww-wpmh vLLM: Permissive default CORS origins allow cross-origin requests to the vLLM HTTP server | Medium | Also affects vllm |
| GHSA-q97c-p368-75c4 vLLM: Out-of-vocabulary prompt token id passes input validation and triggers a CUDA device-side assert (engine crash / DoS) | Medium | Also affects vllm |
| GHSA-h3rc-j82p-2c3h vLLM: Untrusted EPS inputs can trigger denial of service or Ghostscript RCE | High | Also affects vllm |
| GHSA-gq5q-89c7-vhvx vLLM DeepSeek-OCR image processing vulnerable to denial of service | Medium | Also affects vllm |
| GHSA-6757-hggm-hp2w vLLM: Public multimodal SHM handle forgery reaches worker-side `pickle.loads()` with insecure serialization disabled | High | Also affects vllm |
| GHSA-542m-9234-8gvf vLLM: DoS via unbounded chat_template Jinja2 rendering exhausts the renderer thread pool | Medium | Also affects vllm |
MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.
Check your repos with MergeHarbor