CVE-2026-34753: vLLM Server-Side Request Forgery (SSRF) Vulnerability
MODERATECVE-2026-34753 is a MODERATE (CVSS 5.4) server-side request forgery (ssrf) vulnerability (CWE-918: Server-Side Request Forgery (SSRF)) affecting vLLM libraries in the PyPI ecosystem. The issue involves server-side request forgery (ssrf) in `download_bytes_from_url `. Remediation is available by upgrading affected packages to version 0.19.0.
CVE-2026-34753 at a Glance
Security Impact
CVSS: 5.4
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:L).
How to Fix CVE-2026-34753
To remediate this vulnerability, upgrade all affected vLLM dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
pip install vllm>=0.19.0
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
pip show vllm
How to Check if You Are Affected
To determine whether your Python environment is affected by CVE-2026-34753, inspect your installed packages using pip:
pip show vllm
Analyze the terminal output to locate any version of vLLM in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 0.19.0), your application is affected.
Note: The pip show vllm command checks currently installed packages in your active Python environment.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-918: Server-Side Request Forgery (SSRF), where 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. In vLLM, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-34753 Affected vLLM Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
vllm
|
PyPI |
≥ 0.16.0
< 0.19.0
|
0.19.0
|
References
Frequently Asked Questions
What is CVE-2026-34753?
Which vLLM versions are affected?
What version fixes CVE-2026-34753?
Is vllm affected?
What is the CVSS score of CVE-2026-34753?
What CWE is associated with CVE-2026-34753?
How can I check PyPI dependencies for CVE-2026-34753?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-pf3h-qjgv-vcpr",
"modified" : "2026-07-17T16:18:19Z",
"published" : "2026-04-03T21:51:00Z",
"aliases" : [ "CVE-2026-34753" ],
"summary" : "vLLM: Server-Side Request Forgery (SSRF) in `download_bytes_from_url `",
"details" : "### Summary\n\nA Server Side Request Forgery (SSRF) vulnerability in `download_bytes_from_url` allows any actor who can control batch input JSON to make the vLLM batch runner issue arbitrary HTTP/HTTPS requests from the server, without any URL validation or domain restrictions.\n\nThis can be used to target internal services (e.g. cloud metadata endpoints or internal HTTP APIs) reachable from the vLLM host.\n\n------\n\n### Details\n\n#### Vulnerable component\n\nThe vulnerable logic is in the batch runner entrypoint `vllm/entrypoints/openai/run_batch.py`, function `download_bytes_from_url`:\n\n```\n# run_batch.py Lines 442-482\nasync def download_bytes_from_url(url: str) -> bytes:\n \"\"\"\n Download data from a URL or decode from a data URL.\n\n Args:\n url: Either an HTTP/HTTPS URL or a data URL (data:...;base64,...)\n\n Returns:\n Data as bytes\n \"\"\"\n parsed = urlparse(url)\n\n # Handle data URLs (base64 encoded)\n if parsed.scheme == \"data\":\n # Format: data:...;base64,<base64_data>\n if \",\" in url:\n header, data = url.split(\",\", 1)\n if \"base64\" in header:\n return base64.b64decode(data)\n else:\n raise ValueError(f\"Unsupported data URL encoding: {header}\")\n else:\n raise ValueError(f\"Invalid data URL format: {url}\")\n\n # Handle HTTP/HTTPS URLs\n elif parsed.scheme in (\"http\", \"https\"):\n async with (\n aiohttp.ClientSession() as session,\n session.get(url) as resp,\n ):\n if resp.status != 200:\n raise Exception(\n f\"Failed to download data from URL: {url}. Status: {resp.status}\"\n )\n return await resp.read()\n\n else:\n raise ValueError(\n f\"Unsupported URL scheme: {parsed.scheme}. \"\n \"Supported schemes: http, https, data\"\n )\n```\n\nKey properties:\n\n- The function only parses the URL to dispatch on the scheme (`data`, `http`, `https`).\n- For `http` / `https`, it directly calls `session.get(url)` on the provided string.\n- There is no validation of:\n - hostname or IP address,\n - whether the target is internal or external,\n - port number,\n - path, query, or redirect target.\n- This is in contrast to the multimodal media path (`MediaConnector`), which implements an explicit domain allowlist. `download_bytes_from_url` does not reuse that protection.\n\n#### URL controllability\n\nThe `url` argument is fully controlled by batch input JSON via the `file_url` field of `BatchTranscriptionRequest` / `BatchTranslationRequest`.\n\n1. Batch request body type:\n\n```\n# run_batch.py Line 67-80\nclass BatchTranscriptionRequest(TranscriptionRequest):\n \"\"\"\n Batch transcription request that uses file_url instead of file.\n\n This class extends TranscriptionRequest but replaces the file field\n with file_url to support batch processing from audio files written in JSON format.\n \"\"\"\n\n file_url: str = Field(\n ...,\n description=(\n \"Either a URL of the audio or a data URL with base64 encoded audio data. \"\n ),\n )\n```\n\n```\n# run_batch.py Line 98-111\nclass BatchTranslationRequest(TranslationRequest):\n \"\"\"\n Batch translation request that uses file_url instead of file.\n\n This class extends TranslationRequest but replaces the file field\n with file_url to support batch processing from audio files written in JSON format.\n \"\"\"\n\n file_url: str = Field(\n ...,\n description=(\n \"Either a URL of the audio or a data URL with base64 encoded audio data. \"\n ),\n )\n```\n\nThere is no restriction on the domain, IP, or port of `file_url` in these models.\n\n1. Batch input is parsed directly from the batch file:\n\n```\n# run_batch.py Line 139-179\nclass BatchRequestInput(OpenAIBaseModel):\n ...\n url: str\n body: BatchRequestInputBody\n @field_validator(\"body\", mode=\"plain\")\n @classmethod\n def check_type_for_url(cls, value: Any, info: ValidationInfo):\n url: str = info.data[\"url\"]\n ...\n if url == \"/v1/audio/transcriptions\":\n return BatchTranscriptionRequest.model_validate(value)\n if url == \"/v1/audio/translations\":\n return BatchTranslationRequest.model_validate(value)\n```\n\n```\n# run_batch.py Line 770-781\n logger.info(\"Reading batch from %s...\", args.input_file)\n\n # Submit all requests in the file to the engine \"concurrently\".\n response_futures: list[Awaitable[BatchRequestOutput]] = []\n for request_json in (await read_file(args.input_file)).strip().split(\"\\n\"):\n # Skip empty lines.\n request_json = request_json.strip()\n if not request_json:\n continue\n\n request = BatchRequestInput.model_validate_json(request_json)\n```\n\nThe batch runner reads each line of the input file (`args.input_file`), parses it as JSON, and constructs a `BatchTranscriptionRequest` / `BatchTranslationRequest`. Whatever `file_url` appears in that JSON line becomes `batch_request_body.file_url`.\n\n1. `file_url` is passed directly into `download_bytes_from_url`:\n\n```\n# run_batch.py Line 610-623\ndef wrapper(handler_fn: Callable):\n async def transcription_wrapper(\n batch_request_body: (BatchTranscriptionRequest | BatchTranslationRequest),\n ) -> (\n TranscriptionResponse\n | TranscriptionResponseVerbose\n | TranslationResponse\n | TranslationResponseVerbose\n | ErrorResponse\n ):\n try:\n # Download data from URL\n audio_data = await download_bytes_from_url(batch_request_body.file_url)\n```\n\nSo the data flow is:\n\n1. Attacker supplies JSON line in the batch input file with arbitrary `body.file_url`.\n2. `BatchRequestInput` / `BatchTranscriptionRequest` / `BatchTranslationRequest` parse that JSON and store `file_url` verbatim.\n3. `make_transcription_wrapper` calls `download_bytes_from_url(batch_request_body.file_url)`.\n4. `download_bytes_from_url`’s HTTP/HTTPS branch issues `aiohttp.ClientSession().get(url)` to that attacker-controlled URL with no further validation.\n\nThis is a classic SSRF pattern: a server-side component makes arbitrary HTTP requests to a URL string taken from untrusted input.\n\n#### Comparison with safer code\n\nThe project already contains a safer URL-handling path for multimodal media in `vllm/multimodal/media/connector.py`, which demonstrates the intent to mitigate SSRF via domain allowlists and URL normalization:\n\n```\n# connector.py Lines 169-189\n def load_from_url(\n self,\n url: str,\n media_io: MediaIO[_M],\n *,\n fetch_timeout: int | None = None,\n ) -> _M: # type: ignore[type-var]\n url_spec = parse_url(url)\n\n if url_spec.scheme and url_spec.scheme.startswith(\"http\"):\n self._assert_url_in_allowed_media_domains(url_spec)\n\n connection = self.connection\n data = connection.get_bytes(\n url_spec.url,\n timeout=fetch_timeout,\n allow_redirects=envs.VLLM_MEDIA_URL_ALLOW_REDIRECTS,\n )\n\n return media_io.load_bytes(data)\n```\n\nand:\n\n```\n# connector.py Lines 158-167\n def _assert_url_in_allowed_media_domains(self, url_spec: Url) -> None:\n if (\n self.allowed_media_domains\n and url_spec.hostname not in self.allowed_media_domains\n ):\n raise ValueError(\n f\"The URL must be from one of the allowed domains: \"\n f\"{self.allowed_media_domains}. Input URL domain: \"\n f\"{url_spec.hostname}\"\n )\n```\n\n`download_bytes_from_url` does not reuse this allowlist or any equivalent validation, even though it also fetches user-provided URLs.",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:L"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "PyPI",
"name" : "vllm"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0.16.0"
}, {
"fixed" : "0.19.0"
} ]
} ]
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/vllm-project/vllm/security/advisories/GHSA-pf3h-qjgv-vcpr"
}, {
"type" : "ADVISORY",
"url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-34753"
}, {
"type" : "WEB",
"url" : "https://github.com/vllm-project/vllm/pull/38482"
}, {
"type" : "WEB",
"url" : "https://github.com/vllm-project/vllm/commit/57861ae48d3493fa48b4d7d830b7ec9f995783e7"
}, {
"type" : "ADVISORY",
"url" : "https://github.com/advisories/GHSA-pf3h-qjgv-vcpr"
}, {
"type" : "WEB",
"url" : "https://github.com/pypa/advisory-database/tree/main/vulns/vllm/PYSEC-2026-3410.yaml"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/vllm-project/vllm"
}, {
"type" : "WEB",
"url" : "https://pypi.org/project/vllm"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-918" ],
"severity" : "MODERATE",
"github_reviewed" : true,
"github_reviewed_at" : "2026-04-03T21:51:00Z",
"nvd_published_at" : "2026-04-06T16:16:36Z"
}
}