Every CVE Search/ Get API key

CVE-2026-73559

vLLM: Completion prompt lists fan out into unbounded engine requests

6.5 MediumFix available

vLLM is an inference and serving engine for large language models. From 0.19.0 until 0.26.0, the /v1/completions CompletionRequest.prompt field in vllm/entrypoints/openai/completion/protocol.py accepts an unbounded list[str] or list[list[int]], prompt_to_seq() in vllm/renderers/inputs/preprocess.py and OnlineRenderer.preprocess_completion() in vllm/renderers/online_renderer.py expand every element, and vllm/entrypoints/openai/completion/serving.py creates one engine generator and response slot per prompt, allowing an authenticated API client to exhaust CPU, memory, async scheduling capacity, engine request slots, and response buffering with one request. 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
High

Exploit likelihood

0.58%Up 0.2 points since Aug 14, 2026
90%50%10%1%

What each source reports

CVE List Score used 6.5

vLLM: Completion prompt lists fan out into unbounded engine requests

vLLM is an inference and serving engine for large language models. From 0.19.0 until 0.26.0, the /v1/completions CompletionRequest.prompt field in vllm/entrypoints/openai/completion/protocol.py accepts an unbounded list[str] or list[list[int]], prompt_to_seq() in vllm/renderers/inputs/preprocess.py and OnlineRenderer.preprocess_completion() in vllm/renderers/online_renderer.py expand every element, and vllm/entrypoints/openai/completion/serving.py creates one engine generator and response slot per prompt, allowing an authenticated API client to exhaust CPU, memory, async scheduling capacity, engine request slots, and response buffering with one request. This issue is fixed in version 0.26.0.

Record
CVE-2026-73559
CVSS 3.1
6.5 MediumCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H
Weakness
CWE-400
Updated
2026-11-02
EUVDEUVD-2026-58067 6.5

vLLM is an inference and serving engine for large language models. From 0.19.0 until 0.26.0, the /v1/completions CompletionRequest.prompt field in vllm/entrypoints/openai/completion/protocol.py accepts an unbounded list[str] or list[list[int]], prompt_to_seq() in vllm/renderers/inputs/preprocess.py and OnlineRenderer.preprocess_completion() in vllm/renderers/online_renderer.py expand every element, and vllm/entrypoints/openai/completion/serving.py creates one engine generator and response slot per prompt, allowing an authenticated API client to exhaust CPU, memory, async scheduling capacity, engine request slots, and response buffering with one request. This issue is fixed in version 0.26.0.

Record
EUVD-2026-58067
CVSS 3.1
6.5 MediumCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H
Updated
2026-11-02
GitHub AdvisoriesGHSA-87x5-vmc3-756j 6.5

vLLM: Completion prompt lists fan out into unbounded engine requests

Summary

The /v1/completions request model accepts prompt as a list of text prompts or a list of token-id prompts without any outer prompt-count bound. The serving path turns each element into a separate engine input, creates one engine generator per element, merges all generators, and allocates a response slot per prompt. An authenticated API client can therefore turn one request into an attacker-chosen number of backend subrequests before any aggregate request-count budget is enforced.

Technical Details

CompletionRequest.prompt allows both list-shaped prompt inputs and scalar prompts:

# vllm/entrypoints/openai/completion/protocol.py
prompt: (
    list[Annotated[int, Field(ge=0)]]
    | list[list[Annotated[int, Field(ge=0)]]]
    | str
    | list[str]
    | None
) = None

The validator only requires some prompt-like input to be present:

def validate_prompt_and_prompt_embeds(cls, data):
    prompt = data.get("prompt")
    prompt_embeds = data.get("prompt_embeds")
    ...
    if prompt_is_empty and embeds_is_empty:
        raise VLLMValidationError(...)

The renderer then expands list-shaped prompts as a sequence. prompt_to_seq() wraps a scalar string or a single token-id list, but returns a list[str] or list[list[int]] unchanged:

# vllm/renderers/inputs/preprocess.py
def prompt_to_seq(prompt_or_prompts):
    if isinstance(prompt_or_prompts, (dict, str, bytes)) or (
        len(prompt_or_prompts) > 0 and is_list_of(prompt_or_prompts, int)
    ):
        return [prompt_or_prompts]

    return prompt_or_prompts

OnlineRenderer.preprocess_completion() appends that whole sequence, and the renderer processes every element:

# vllm/renderers/online_renderer.py
prompts = list[SingletonPrompt | bytes]()
if prompt_input is not None:
    prompts.extend(prompt_to_seq(prompt_input))
...
parsed_prompts = [
    prompt if isinstance(prompt, bytes) else parse_model_prompt(model_config, prompt)
    for prompt in prompts
]
return await renderer.render_cmpl_async(parsed_prompts, tok_params, ...)

Finally, completion serving creates one backend generator and one response slot per rendered prompt:

# vllm/entrypoints/openai/completion/serving.py
generators: list[AsyncGenerator[RequestOutput, None]] = []
for i, engine_input in enumerate(engine_inputs):
    ...
    generator = self.engine_client.generate(...)
    generators.append(generator)

result_generator = merge_async_iterators(*generators)
num_prompts = len(engine_inputs)
...
final_res_batch: list[RequestOutput | None] = [None] * num_prompts

The violated invariant is that one HTTP request should have a bounded backend request count. Current code enforces per-prompt token and sampling limits, but not the number of prompts in the outer completion request.

PoV

A minimal oversized request keeps normal generation parameters small but supplies a large outer prompt list:

{
  "model": "served-model",
  "prompt": ["x", "x", "x"],
  "max_tokens": 1,
  "n": 1
}

Scaling the prompt array to tens or hundreds of thousands of short entries makes the server allocate, preprocess, schedule, merge, and buffer one subrequest per entry. The same applies to token-id prompt lists:

{
  "model": "served-model",
  "prompt": [[1], [1], [1]],
  "max_tokens": 1,
  "n": 1
}

The intended negative control is a scalar prompt:

{
  "model": "served-model",
  "prompt": "x",
  "max_tokens": 1,
  "n": 1
}

The scalar string is wrapped as one prompt; the list form is not bounded and fans out by list length.

Impact

An authenticated API client can make one /v1/completions request consume CPU, memory, async task scheduling, engine request slots, and response buffering proportional to an attacker-chosen outer prompt list. This can starve or disrupt other tenants sharing the same vLLM server. The report does not claim unauthenticated access, confidentiality impact, integrity impact, code…

Record
GHSA-87x5-vmc3-756j
CVSS 3.1
6.5 MediumCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H
Weakness
CWE-400
Updated
2026-11-02
NVD 6.5

vLLM is an inference and serving engine for large language models. From 0.19.0 until 0.26.0, the /v1/completions CompletionRequest.prompt field in vllm/entrypoints/openai/completion/protocol.py accepts an unbounded list[str] or list[list[int]], prompt_to_seq() in vllm/renderers/inputs/preprocess.py and OnlineRenderer.preprocess_completion() in vllm/renderers/online_renderer.py expand every element, and vllm/entrypoints/openai/completion/serving.py creates one engine generator and response slot per prompt, allowing an authenticated API client to exhaust CPU, memory, async scheduling capacity, engine request slots, and response buffering with one request. This issue is fixed in version 0.26.0.

Record
CVE-2026-73559
CVSS 3.1
6.5 MediumCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H
Weakness
CWE-400
Updated
2026-11-02
OSV 6.5

vLLM: Completion prompt lists fan out into unbounded engine requests

vLLM is an inference and serving engine for large language models. From 0.19.0 until 0.26.0, the /v1/completions CompletionRequest.prompt field in vllm/entrypoints/openai/completion/protocol.py accepts an unbounded list[str] or list[list[int]], prompt_to_seq() in vllm/renderers/inputs/preprocess.py and OnlineRenderer.preprocess_completion() in vllm/renderers/online_renderer.py expand every element, and vllm/entrypoints/openai/completion/serving.py creates one engine generator and response slot per prompt, allowing an authenticated API client to exhaust CPU, memory, async scheduling capacity, engine request slots, and response buffering with one request. This issue is fixed in version 0.26.0.

Record
CVE-2026-73559
CVSS 3.1
6.5 MediumCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H
Weakness
CWE-400
Updated
2026-11-04
OSVGHSA-87x5-vmc3-756j 6.5

vLLM: Completion prompt lists fan out into unbounded engine requests

Summary

The /v1/completions request model accepts prompt as a list of text prompts or a list of token-id prompts without any outer prompt-count bound. The serving path turns each element into a separate engine input, creates one engine generator per element, merges all generators, and allocates a response slot per prompt. An authenticated API client can therefore turn one request into an attacker-chosen number of backend subrequests before any aggregate request-count budget is enforced.

Technical Details

CompletionRequest.prompt allows both list-shaped prompt inputs and scalar prompts:

# vllm/entrypoints/openai/completion/protocol.py
prompt: (
    list[Annotated[int, Field(ge=0)]]
    | list[list[Annotated[int, Field(ge=0)]]]
    | str
    | list[str]
    | None
) = None

The validator only requires some prompt-like input to be present:

def validate_prompt_and_prompt_embeds(cls, data):
    prompt = data.get("prompt")
    prompt_embeds = data.get("prompt_embeds")
    ...
    if prompt_is_empty and embeds_is_empty:
        raise VLLMValidationError(...)

The renderer then expands list-shaped prompts as a sequence. prompt_to_seq() wraps a scalar string or a single token-id list, but returns a list[str] or list[list[int]] unchanged:

# vllm/renderers/inputs/preprocess.py
def prompt_to_seq(prompt_or_prompts):
    if isinstance(prompt_or_prompts, (dict, str, bytes)) or (
        len(prompt_or_prompts) > 0 and is_list_of(prompt_or_prompts, int)
    ):
        return [prompt_or_prompts]

    return prompt_or_prompts

OnlineRenderer.preprocess_completion() appends that whole sequence, and the renderer processes every element:

# vllm/renderers/online_renderer.py
prompts = list[SingletonPrompt | bytes]()
if prompt_input is not None:
    prompts.extend(prompt_to_seq(prompt_input))
...
parsed_prompts = [
    prompt if isinstance(prompt, bytes) else parse_model_prompt(model_config, prompt)
    for prompt in prompts
]
return await renderer.render_cmpl_async(parsed_prompts, tok_params, ...)

Finally, completion serving creates one backend generator and one response slot per rendered prompt:

# vllm/entrypoints/openai/completion/serving.py
generators: list[AsyncGenerator[RequestOutput, None]] = []
for i, engine_input in enumerate(engine_inputs):
    ...
    generator = self.engine_client.generate(...)
    generators.append(generator)

result_generator = merge_async_iterators(*generators)
num_prompts = len(engine_inputs)
...
final_res_batch: list[RequestOutput | None] = [None] * num_prompts

The violated invariant is that one HTTP request should have a bounded backend request count. Current code enforces per-prompt token and sampling limits, but not the number of prompts in the outer completion request.

PoV

A minimal oversized request keeps normal generation parameters small but supplies a large outer prompt list:

{
  "model": "served-model",
  "prompt": ["x", "x", "x"],
  "max_tokens": 1,
  "n": 1
}

Scaling the prompt array to tens or hundreds of thousands of short entries makes the server allocate, preprocess, schedule, merge, and buffer one subrequest per entry. The same applies to token-id prompt lists:

{
  "model": "served-model",
  "prompt": [[1], [1], [1]],
  "max_tokens": 1,
  "n": 1
}

The intended negative control is a scalar prompt:

{
  "model": "served-model",
  "prompt": "x",
  "max_tokens": 1,
  "n": 1
}

The scalar string is wrapped as one prompt; the list form is not bounded and fans out by list length.

Impact

An authenticated API client can make one /v1/completions request consume CPU, memory, async task scheduling, engine request slots, and response buffering proportional to an attacker-chosen outer prompt list. This can starve or disrupt other tenants sharing the same vLLM server. The report does not claim unauthenticated access, confidentiality impact, integrity impact, code…

Record
GHSA-87x5-vmc3-756j
CVSS 3.1
6.5 MediumCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H
Weakness
CWE-400
Updated
2026-11-02
OSVPYSEC-2026-3704 6.5

vLLM: Completion prompt lists fan out into unbounded engine requests

Summary

The /v1/completions request model accepts prompt as a list of text prompts or a list of token-id prompts without any outer prompt-count bound. The serving path turns each element into a separate engine input, creates one engine generator per element, merges all generators, and allocates a response slot per prompt. An authenticated API client can therefore turn one request into an attacker-chosen number of backend subrequests before any aggregate request-count budget is enforced.

Technical Details

CompletionRequest.prompt allows both list-shaped prompt inputs and scalar prompts:

# vllm/entrypoints/openai/completion/protocol.py
prompt: (
    list[Annotated[int, Field(ge=0)]]
    | list[list[Annotated[int, Field(ge=0)]]]
    | str
    | list[str]
    | None
) = None

The validator only requires some prompt-like input to be present:

def validate_prompt_and_prompt_embeds(cls, data):
    prompt = data.get("prompt")
    prompt_embeds = data.get("prompt_embeds")
    ...
    if prompt_is_empty and embeds_is_empty:
        raise VLLMValidationError(...)

The renderer then expands list-shaped prompts as a sequence. prompt_to_seq() wraps a scalar string or a single token-id list, but returns a list[str] or list[list[int]] unchanged:

# vllm/renderers/inputs/preprocess.py
def prompt_to_seq(prompt_or_prompts):
    if isinstance(prompt_or_prompts, (dict, str, bytes)) or (
        len(prompt_or_prompts) > 0 and is_list_of(prompt_or_prompts, int)
    ):
        return [prompt_or_prompts]

    return prompt_or_prompts

OnlineRenderer.preprocess_completion() appends that whole sequence, and the renderer processes every element:

# vllm/renderers/online_renderer.py
prompts = list[SingletonPrompt | bytes]()
if prompt_input is not None:
    prompts.extend(prompt_to_seq(prompt_input))
...
parsed_prompts = [
    prompt if isinstance(prompt, bytes) else parse_model_prompt(model_config, prompt)
    for prompt in prompts
]
return await renderer.render_cmpl_async(parsed_prompts, tok_params, ...)

Finally, completion serving creates one backend generator and one response slot per rendered prompt:

# vllm/entrypoints/openai/completion/serving.py
generators: list[AsyncGenerator[RequestOutput, None]] = []
for i, engine_input in enumerate(engine_inputs):
    ...
    generator = self.engine_client.generate(...)
    generators.append(generator)

result_generator = merge_async_iterators(*generators)
num_prompts = len(engine_inputs)
...
final_res_batch: list[RequestOutput | None] = [None] * num_prompts

The violated invariant is that one HTTP request should have a bounded backend request count. Current code enforces per-prompt token and sampling limits, but not the number of prompts in the outer completion request.

PoV

A minimal oversized request keeps normal generation parameters small but supplies a large outer prompt list:

{
  "model": "served-model",
  "prompt": ["x", "x", "x"],
  "max_tokens": 1,
  "n": 1
}

Scaling the prompt array to tens or hundreds of thousands of short entries makes the server allocate, preprocess, schedule, merge, and buffer one subrequest per entry. The same applies to token-id prompt lists:

{
  "model": "served-model",
  "prompt": [[1], [1], [1]],
  "max_tokens": 1,
  "n": 1
}

The intended negative control is a scalar prompt:

{
  "model": "served-model",
  "prompt": "x",
  "max_tokens": 1,
  "n": 1
}

The scalar string is wrapped as one prompt; the list form is not bounded and fans out by list length.

Impact

An authenticated API client can make one /v1/completions request consume CPU, memory, async task scheduling, engine request slots, and response buffering proportional to an attacker-chosen outer prompt list. This can starve or disrupt other tenants sharing the same vLLM server. The report does not claim unauthenticated access, confidentiality impact, integrity impact, code…

Record
PYSEC-2026-3704
CVSS 3.1
6.5 MediumCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
vllm
PyPI
0.19.0 up to 0.26.0
0.26.0
vllm
PyPI
0.19.0 up to 0.26.0, plus listed versions
0.19.10.20.00.20.10.20.20.21.0
Show 6 moreShow less
0.22.00.22.10.23.00.24.00.25.00.25.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-73559 is a medium vulnerability (CVSS 6.5) in Vllm, published August 13, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.58%, the 48th percentile. Fixed in vllm 0.26.0.

References

This page doesn't list everything

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

MoveEnter Open