Skip to content

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

Medium PyPI
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

  1. SSRF — internal-network probing / cloud-metadata theft. Standard requests.get follows redirects and accepts any URL. An attacker who controls a multi_modal_data value 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.
  2. Arbitrary local file read via file://path (line 242) and the unguarded fallback Image.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.
  3. 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

PackageVulnerableFixed 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

Timeline

  1. Published
  2. Last updated by the source
  3. Latest EPSS score
AdvisorySeverityWhy 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
Is this in your code?

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