Every CVE Search/ Get API key

CVE-2026-71486

vLLM: Derender endpoints decode caller-supplied GenerateResponse token IDs without output bounds

4.3 MediumFix available

vLLM is an inference and serving engine for large language models. Prior to 0.26.0, the /v1/completions/derender and /v1/chat/completions/derender endpoints accept caller-supplied GenerateResponse objects whose generate_responses, choices, token_ids, prompt_logprobs, logprobs.content, top_logprobs, and routed_experts structures are processed by OnlineDerenderer and tokenizer.decode before max_model_len, max_tokens, max_num_seqs, or response-size limits are enforced, allowing an authenticated API client to consume excessive CPU and memory and produce oversized responses. This issue is fixed in version 0.26.0.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
Low
Privileges needed
Low
User interaction
None

What an attacker can affect

Confidentiality
None
Integrity
None
Availability
Low

Exploit likelihood

0.37%Unchanged since Aug 18, 2026
90%50%10%1%

What each source reports

CVE List Score used 4.3

vLLM: Derender endpoints decode caller-supplied GenerateResponse token IDs without output bounds

vLLM is an inference and serving engine for large language models. Prior to 0.26.0, the /v1/completions/derender and /v1/chat/completions/derender endpoints accept caller-supplied GenerateResponse objects whose generate_responses, choices, token_ids, prompt_logprobs, logprobs.content, top_logprobs, and routed_experts structures are processed by OnlineDerenderer and tokenizer.decode before max_model_len, max_tokens, max_num_seqs, or response-size limits are enforced, allowing an authenticated API client to consume excessive CPU and memory and produce oversized responses. This issue is fixed in version 0.26.0.

Record
CVE-2026-71486
CVSS 3.1
4.3 MediumCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L
Weakness
CWE-400, CWE-770
Updated
2026-11-02
EUVDEUVD-2026-60376 4.3

vLLM is an inference and serving engine for large language models. Prior to 0.26.0, the /v1/completions/derender and /v1/chat/completions/derender endpoints accept caller-supplied GenerateResponse objects whose generate_responses, choices, token_ids, prompt_logprobs, logprobs.content, top_logprobs, and routed_experts structures are processed by OnlineDerenderer and tokenizer.decode before max_model_len, max_tokens, max_num_seqs, or response-size limits are enforced, allowing an authenticated API client to consume excessive CPU and memory and produce oversized responses. This issue is fixed in version 0.26.0.

Record
EUVD-2026-60376
CVSS 3.1
4.3 MediumCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L
Updated
2026-11-02
GitHub AdvisoriesGHSA-8737-qx52-hjff 4.3

vLLM: Derender endpoints decode caller-supplied GenerateResponse token IDs without output bounds

Summary

The /v1/completions/derender and /v1/chat/completions/derender endpoints accept caller-supplied GenerateResponse objects and postprocess every nested choices[*].token_ids list directly. Unlike the normal render/generate path, derender does not enforce model context length, resolved max_tokens, max_num_seqs, choice-count, or response-size bounds before detokenizing and returning the supplied token IDs. An authenticated API client can therefore make the CPU-only render frontend, or any server exposing these /v1 derender routes, spend CPU and memory proportional to attacker-chosen generated-output-shaped JSON rather than to a bounded generation result.

Technical Details

The render router registers /v1/chat/completions/derender and /v1/completions/derender in vllm/entrypoints/serve/render/api_router.py, and the OpenAI API server attaches this router whenever "generate" or "render" is in supported_tasks (vllm/entrypoints/openai/api_server.py). The routes are under /v1, so they are part of the OpenAI-compatible HTTP API surface and are protected by the API-key middleware when --api-key is configured.

The request types trust generated-output-shaped data from the client. In vllm/entrypoints/serve/disagg/protocol.py, GenerateResponseChoice accepts token_ids: list[int] | None = None, GenerateResponse accepts choices: list[GenerateResponseChoice], and DerenderCompletionRequest accepts generate_responses: list[GenerateResponse]. These fields have no max length, max item count, or relationship to a prior GenerateRequest.

The sink is OnlineDerenderer. derender_completion() iterates every supplied generate_responses entry and every nested choice, calls tokenizer.decode(choice.token_ids, skip_special_tokens=True), appends the decoded text to the response choices, and increments total_completion_tokens from the same supplied list length. derender_chat() has the same shape for a single supplied generate_response, and can also feed the decoded text into tool/reasoning parsers when a parser and chat_request are present. ServingRender.derender_completion_response() calls online_derenderer.derender_completion(request.generate_responses, request.prompt_tokens) before applying any completion-level validation beyond the model check.

Normal render and generation paths derive output limits from max_model_len, the rendered prompt length, request max_tokens / max_completion_tokens, and scheduler limits. Derender bypasses that invariant because it accepts the already-generated output shape directly from the HTTP caller. The missing invariant is: derender should only postprocess bounded generated output, and client-supplied derender payloads must be rejected if their nested generated token/logprob structures exceed the same limits that generation would have enforced.

PoV

The following bounded PoV can be run from a current vLLM checkout containing PR #43606. It asserts the current source facts for the derender routes, unchecked request fields, and decode sink, then simulates the same derender loop with a counting tokenizer. The negative control is a one-choice, 32-token response. The amplified payload keeps the test bounded but demonstrates that all decoded work and returned text scale directly with caller-supplied GenerateResponse contents.

#!/usr/bin/env python3
import subprocess
from dataclasses import dataclass
from pathlib import Path

SOURCE = Path(".")

def require_source_fact(path: str, needles: list[str]) -> None:
    text = (SOURCE / path).read_text()
    missing = [needle for needle in needles if needle not in text]
    if missing:
        raise AssertionError(f"{path} missing expected facts: {missing}")

def source_head() -> str:
    return subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=SOURCE, text=True).strip()

@dataclass
class Choice:
    index: int
    token_ids: list[int]

@dataclass
class GenerateResponse:
   …
Record
GHSA-8737-qx52-hjff
CVSS 3.1
4.3 MediumCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L
Weakness
CWE-400, CWE-770
Updated
2026-11-02
NVD 4.3

vLLM is an inference and serving engine for large language models. Prior to 0.26.0, the /v1/completions/derender and /v1/chat/completions/derender endpoints accept caller-supplied GenerateResponse objects whose generate_responses, choices, token_ids, prompt_logprobs, logprobs.content, top_logprobs, and routed_experts structures are processed by OnlineDerenderer and tokenizer.decode before max_model_len, max_tokens, max_num_seqs, or response-size limits are enforced, allowing an authenticated API client to consume excessive CPU and memory and produce oversized responses. This issue is fixed in version 0.26.0.

Record
CVE-2026-71486
CVSS 3.1
4.3 MediumCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L
Weakness
CWE-400, CWE-770
Updated
2026-11-02
OSV 4.3

vLLM: Derender endpoints decode caller-supplied GenerateResponse token IDs without output bounds

vLLM is an inference and serving engine for large language models. Prior to 0.26.0, the /v1/completions/derender and /v1/chat/completions/derender endpoints accept caller-supplied GenerateResponse objects whose generate_responses, choices, token_ids, prompt_logprobs, logprobs.content, top_logprobs, and routed_experts structures are processed by OnlineDerenderer and tokenizer.decode before max_model_len, max_tokens, max_num_seqs, or response-size limits are enforced, allowing an authenticated API client to consume excessive CPU and memory and produce oversized responses. This issue is fixed in version 0.26.0.

Record
CVE-2026-71486
CVSS 3.1
4.3 MediumCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L
Weakness
CWE-400, CWE-770
Updated
2026-11-04
OSVGHSA-8737-qx52-hjff 4.3

vLLM: Derender endpoints decode caller-supplied GenerateResponse token IDs without output bounds

Summary

The /v1/completions/derender and /v1/chat/completions/derender endpoints accept caller-supplied GenerateResponse objects and postprocess every nested choices[*].token_ids list directly. Unlike the normal render/generate path, derender does not enforce model context length, resolved max_tokens, max_num_seqs, choice-count, or response-size bounds before detokenizing and returning the supplied token IDs. An authenticated API client can therefore make the CPU-only render frontend, or any server exposing these /v1 derender routes, spend CPU and memory proportional to attacker-chosen generated-output-shaped JSON rather than to a bounded generation result.

Technical Details

The render router registers /v1/chat/completions/derender and /v1/completions/derender in vllm/entrypoints/serve/render/api_router.py, and the OpenAI API server attaches this router whenever "generate" or "render" is in supported_tasks (vllm/entrypoints/openai/api_server.py). The routes are under /v1, so they are part of the OpenAI-compatible HTTP API surface and are protected by the API-key middleware when --api-key is configured.

The request types trust generated-output-shaped data from the client. In vllm/entrypoints/serve/disagg/protocol.py, GenerateResponseChoice accepts token_ids: list[int] | None = None, GenerateResponse accepts choices: list[GenerateResponseChoice], and DerenderCompletionRequest accepts generate_responses: list[GenerateResponse]. These fields have no max length, max item count, or relationship to a prior GenerateRequest.

The sink is OnlineDerenderer. derender_completion() iterates every supplied generate_responses entry and every nested choice, calls tokenizer.decode(choice.token_ids, skip_special_tokens=True), appends the decoded text to the response choices, and increments total_completion_tokens from the same supplied list length. derender_chat() has the same shape for a single supplied generate_response, and can also feed the decoded text into tool/reasoning parsers when a parser and chat_request are present. ServingRender.derender_completion_response() calls online_derenderer.derender_completion(request.generate_responses, request.prompt_tokens) before applying any completion-level validation beyond the model check.

Normal render and generation paths derive output limits from max_model_len, the rendered prompt length, request max_tokens / max_completion_tokens, and scheduler limits. Derender bypasses that invariant because it accepts the already-generated output shape directly from the HTTP caller. The missing invariant is: derender should only postprocess bounded generated output, and client-supplied derender payloads must be rejected if their nested generated token/logprob structures exceed the same limits that generation would have enforced.

PoV

The following bounded PoV can be run from a current vLLM checkout containing PR #43606. It asserts the current source facts for the derender routes, unchecked request fields, and decode sink, then simulates the same derender loop with a counting tokenizer. The negative control is a one-choice, 32-token response. The amplified payload keeps the test bounded but demonstrates that all decoded work and returned text scale directly with caller-supplied GenerateResponse contents.

#!/usr/bin/env python3
import subprocess
from dataclasses import dataclass
from pathlib import Path

SOURCE = Path(".")

def require_source_fact(path: str, needles: list[str]) -> None:
    text = (SOURCE / path).read_text()
    missing = [needle for needle in needles if needle not in text]
    if missing:
        raise AssertionError(f"{path} missing expected facts: {missing}")

def source_head() -> str:
    return subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=SOURCE, text=True).strip()

@dataclass
class Choice:
    index: int
    token_ids: list[int]

@dataclass
class GenerateResponse:
   …
Record
GHSA-8737-qx52-hjff
CVSS 3.1
4.3 MediumCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L
Weakness
CWE-400, CWE-770
Updated
2026-11-02
OSVPYSEC-2026-3936 4.3

vLLM: Derender endpoints decode caller-supplied GenerateResponse token IDs without output bounds

Summary

The /v1/completions/derender and /v1/chat/completions/derender endpoints accept caller-supplied GenerateResponse objects and postprocess every nested choices[*].token_ids list directly. Unlike the normal render/generate path, derender does not enforce model context length, resolved max_tokens, max_num_seqs, choice-count, or response-size bounds before detokenizing and returning the supplied token IDs. An authenticated API client can therefore make the CPU-only render frontend, or any server exposing these /v1 derender routes, spend CPU and memory proportional to attacker-chosen generated-output-shaped JSON rather than to a bounded generation result.

Technical Details

The render router registers /v1/chat/completions/derender and /v1/completions/derender in vllm/entrypoints/serve/render/api_router.py, and the OpenAI API server attaches this router whenever "generate" or "render" is in supported_tasks (vllm/entrypoints/openai/api_server.py). The routes are under /v1, so they are part of the OpenAI-compatible HTTP API surface and are protected by the API-key middleware when --api-key is configured.

The request types trust generated-output-shaped data from the client. In vllm/entrypoints/serve/disagg/protocol.py, GenerateResponseChoice accepts token_ids: list[int] | None = None, GenerateResponse accepts choices: list[GenerateResponseChoice], and DerenderCompletionRequest accepts generate_responses: list[GenerateResponse]. These fields have no max length, max item count, or relationship to a prior GenerateRequest.

The sink is OnlineDerenderer. derender_completion() iterates every supplied generate_responses entry and every nested choice, calls tokenizer.decode(choice.token_ids, skip_special_tokens=True), appends the decoded text to the response choices, and increments total_completion_tokens from the same supplied list length. derender_chat() has the same shape for a single supplied generate_response, and can also feed the decoded text into tool/reasoning parsers when a parser and chat_request are present. ServingRender.derender_completion_response() calls online_derenderer.derender_completion(request.generate_responses, request.prompt_tokens) before applying any completion-level validation beyond the model check.

Normal render and generation paths derive output limits from max_model_len, the rendered prompt length, request max_tokens / max_completion_tokens, and scheduler limits. Derender bypasses that invariant because it accepts the already-generated output shape directly from the HTTP caller. The missing invariant is: derender should only postprocess bounded generated output, and client-supplied derender payloads must be rejected if their nested generated token/logprob structures exceed the same limits that generation would have enforced.

PoV

The following bounded PoV can be run from a current vLLM checkout containing PR #43606. It asserts the current source facts for the derender routes, unchecked request fields, and decode sink, then simulates the same derender loop with a counting tokenizer. The negative control is a one-choice, 32-token response. The amplified payload keeps the test bounded but demonstrates that all decoded work and returned text scale directly with caller-supplied GenerateResponse contents.

#!/usr/bin/env python3
import subprocess
from dataclasses import dataclass
from pathlib import Path

SOURCE = Path(".")

def require_source_fact(path: str, needles: list[str]) -> None:
    text = (SOURCE / path).read_text()
    missing = [needle for needle in needles if needle not in text]
    if missing:
        raise AssertionError(f"{path} missing expected facts: {missing}")

def source_head() -> str:
    return subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=SOURCE, text=True).strip()

@dataclass
class Choice:
    index: int
    token_ids: list[int]

@dataclass
class GenerateResponse:
   …
Record
PYSEC-2026-3936
CVSS 3.1
4.3 MediumCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
vllm
PyPI
before 0.26.0
0.26.0
vllm
PyPI
before 0.26.0, plus listed versions
0.0.10.1.00.1.10.1.20.1.3
Show 45 moreShow less
0.1.40.1.50.1.60.1.70.10.00.10.10.10.1.10.10.20.11.00.11.10.11.20.12.00.13.00.14.00.14.10.15.00.15.10.16.00.17.00.17.10.18.00.18.10.19.00.19.10.2.00.2.10.2.1.post10.2.20.2.30.2.40.2.50.2.60.2.70.20.00.20.10.20.20.21.00.22.00.22.10.23.00.24.00.25.00.25.10.3.00.3.1
0.26.0

History

History
RecordedChangedSources
factsOSV
first indexedCVE List, EUVD, GitHub Advisories, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-71486 is a medium vulnerability (CVSS 4.3) in Vllm, published August 17, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.37%, the 31st percentile. Fixed in vllm 0.26.0.

References

This page doesn't list everything

Left out here: 42 versions and the rest of 3 source descriptions. The JSON record has them.

MoveEnter Open