CVE-2026-105757

CVE-2026-105757 is a medium-severity improper input validation vulnerability in vllm (pip), affecting versions < 0.30.0. It is fixed in 0.30.0.

Does this CVE actually affect you?

Kodem shows which CVEs are reachable and running in your applications, so you fix what's exploitable, not just what's listed.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Runtime intelligence, not another scanner.

Summary

vLLM: Structured-output request errors escape the request boundary and terminate the shared EngineCore, engine-fatal denial of service (3 sites)

Affected

  • Ecosystem / package: pip / vllm
  • Affected versions: vLLM ≤ 0.25.1 (confirmed on 0.25.1, commit 752a3a504485). The lower bound predates 0.25.1; maintainers can confirm how far back each path reaches.

Three structured-output request paths let an ordinary request reach a condition that raises an uncaught, engine-fatal exception instead of a per-request validation error. The failure is not confined to the request that caused it: it escapes into EngineCore's busy loop and triggers a fatal _send_engine_dead(), so one malformed structured-output request denies service to all concurrent and subsequent tenants of that engine. The requests are ordinary API calls; the client does not need any privileged configuration beyond the (often default) structured-output feature.

The shared root cause is the absence of a per-request exception boundary around structured-output grammar/token handling: a value that should fail as a request-scoped validation error instead propagates as an uncaught exception (or bypasses frontend validation entirely) and reaches the scheduler/engine loop, which treats the failure as fatal.

These sites are distinct from the published fixes for GHSA-6qc9-v4r8-22xg and GHSA-8wr5-jm2h-8r4f: GHSA-6qc9 (PR #17623) added frontend json-schema/regex/type validation but the completion check on v0.25.1 still catches only TimeoutError, so Site 1's valid duplicate-root EBNF via the latched xgrammar backend still re-raises and kills EngineCore; GHSA-8wr5 (PR #44744) fixes recovered-token state in the eagle spec-decode path and explicitly does not treat trailing -1 padding as the fault, so Site 2's -1 reaching backend_guidance.validate_tokens() via ngram_gpu survives it.

Affected code

Links pinned to the confirmed commit 752a3a504485 (v0.25.1). The three sites are:

Site 1, latched-backend compile exception escapes. StructuredOutputManager permanently latches one backend on the first structured-output request and always compiles through it, ignoring the per-request auto backend selection. A grammar that auto accepts only via a fallback backend (e.g. a duplicate-root EBNF) then makes the latched xgrammar compiler raise; the exception is stored in the grammar Future, and the completion check catches only TimeoutError, so Future.result() re-raises during scheduler promotion and kills EngineCore.

The escaping-exception sink: the completion check only handles TimeoutError, so any compile exception stored in the Future re-raises out of result() during scheduler promotion.

# vllm/v1/structured_output/request.py Lines 48-59
    def _check_grammar_completion(self) -> bool:
        # NOTE: We have to lazy import to gate circular imports
        from vllm.v1.request import RequestStatus

        if isinstance(self._grammar, Future):
            try:
                # We will check whether the future is ready within 100 us
                self._grammar = self._grammar.result(timeout=0.0001)
                self.status = RequestStatus.WAITING
            except TimeoutError:
                return False
        return True

Site 2, guidance treats ngram_gpu -1 padding as a token id. The ngram_gpu proposer pads a fixed-width draft-token row with -1 and separately records num_valid_draft_tokens, but the scheduler forwards the untrimmed padded row into GuidanceGrammar.validate_tokens(), which passes -1 to the native llguidance matcher; the matcher cannot convert a negative value to its unsigned token type and raises an OverflowError, uncaught and fatal.

The sink passes the padded row straight to the native matcher with no lower-bound check on token ids:

# vllm/v1/structured_output/backend_guidance.py Lines 181-196
    def validate_tokens(self, tokens: list[int]) -> list[int]:
        """Checks if the list of tokens are accepted by the parser in sequence.
        Will not advance the parser.

        Returns the prefix list of tokens that are accepted by the parser.
        """
        if len(tokens) == 0:
            return []
        if self.ll_matcher.is_stopped():
            return []

        num_tokens = self.ll_matcher.validate_tokens(tokens)

        self.check_error()

        return tokens[:num_tokens]

Site 3, Rust frontend accepts an empty structured-output value the Python frontend rejects. The opt-in Rust frontend admits an empty structured_outputs.json / structured_outputs.grammar string that the Python frontend rejects; the empty value passes through the wire-params conversion without validation, reaches the engine, and marks EngineCore dead.

The conversion maps each field to a constraint with no non-empty check, so an empty json/grammar string is forwarded unchanged:

// rust/src/engine-core-client/src/protocol/structured_outputs.rs Lines 135-162
impl TryFrom<WireStructuredOutputsParams> for StructuredOutputsParams {
    type Error = Error;

    fn try_from(raw: WireStructuredOutputsParams) -> Result<Self> {
        use StructuredOutputConstraint::*;

        let mut constraint = None;

        macro_rules! insert_constraint {
            ($name:literal, $value:expr) => {
                if let Some(value) = $value {
                    if let Some((existing, _)) = constraint {
                        return Err(Error::InvalidStructuredOutputsParams {
                            message: format!(
                                "multiple structured output constraints specified: {existing}, {}",
                                $name
                            ),
                        });
                    }
                    constraint = Some(($name, value));
                }
            };
        }

        insert_constraint!("json", raw.json.map(Json));
        insert_constraint!("regex", raw.regex.map(Regex));
        insert_constraint!("choice", raw.choice.map(Choice));
        insert_constraint!("grammar", raw.grammar.map(Grammar));

Credit

Reported by: Patch the Planet (Trail of Bits + OpenAI collaboration)

These vulnerabilities were discovered using GPT-5.5-Cyber as part of the Patch the Planet security initiative.

Proposed fix: a fix for this issue is proposed in a public pull request: https://github.com/vllm-project/vllm/pull/51450

Impact

Any client able to send an ordinary structured-output request (two requests for Site 1) can terminate the shared EngineCore, denying service to all tenants of that engine instance. Availability only; no code execution, memory corruption, or data disclosure.

  • Site 1 affects the default "auto" structured-output backend; no speculative decoding or non-default configuration is required.
  • Site 2 requires a deployment using the guidance backend together with ngram_gpu speculative decoding; the -1 sentinel is produced by the proposer itself, so an ordinary constrained request suffices, the client does not craft the negative token.
  • Site 3 requires the opt-in Rust frontend; a single request with an empty constraint value is enough. API-key middleware narrows the attacker from unauthenticated to authenticated but does not restore the missing validation.

The application does not adequately validate input before processing it, allowing unexpected values to reach sensitive code paths. Typical impact: varies by context: data corruption, logic bypass, or denial of service.

CVE-2026-105757 has a CVSS score of 6.5 (Medium). The vector is network-reachable, low privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (0.30.0); upgrading removes the vulnerable code path.

Affected versions

vllm (< 0.30.0)

Security releases

vllm → 0.30.0 (pip)

Kodem intelligence

Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.

Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.

Already deployed Kodem?

See it in your environmentNew to Kodem? Get a demo →

Remediation advice

Convert structured-output failures into request-scoped errors at the boundary. Each site is a distinct fix shape; the core hunk for each is below.

Site 1, resolve the backend per request instead of latching one process-wide, so a grammar is always compiled with the backend that auto actually selected for it (and a compile failure is that request's error, not the engine's):

# vllm/v1/structured_output/__init__.py, grammar_init(), replacing the latched-backend path
backend = self._get_or_create_backend(request.structured_output_request.backend)
if self._use_async_grammar_compilation:
    grammar = self.executor.submit(self._create_grammar, request, backend)
else:
    grammar = self._create_grammar(request, backend)

Site 2, drop negative sentinels before handing the row to the native matcher, inside validate_tokens():

# vllm/v1/structured_output/backend_guidance.py, validate_tokens(), before the ll_matcher call
for i, token in enumerate(tokens):
    if token < 0:
        tokens = tokens[:i]
        break
if len(tokens) == 0:
    return []

Site 3, reject empty json/grammar strings in the Rust wire-params conversion, matching the Python frontend, before the value reaches the engine:

// rust/src/engine-core-client/src/protocol/structured_outputs.rs, try_from(), before constraint mapping
if matches!(&raw.json, Some(Value::String(value)) if value.trim().is_empty()) {
    return Err(Error::InvalidStructuredOutputsParams {
        message: "structured_outputs.json cannot be an empty string".to_string(),
    });
}
if matches!(&raw.grammar, Some(value) if value.trim().is_empty()) {
    return Err(Error::InvalidStructuredOutputsParams {
        message: "structured_outputs.grammar cannot be an empty string".to_string(),
    });
}

The three sites share one root cause and the same fix shape (bound the failure to the request), so they are filed as a single advisory; happy to split into per-component advisories if the maintainers prefer. Each fix carries a regression test.

Frequently Asked Questions

  1. What is CVE-2026-105757? CVE-2026-105757 is a medium-severity improper input validation vulnerability in vllm (pip), affecting versions < 0.30.0. It is fixed in 0.30.0. The application does not adequately validate input before processing it, allowing unexpected values to reach sensitive code paths.
  2. How severe is CVE-2026-105757? CVE-2026-105757 has a CVSS score of 6.5 (Medium). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
  3. Which versions of vllm are affected by CVE-2026-105757? vllm (pip) versions < 0.30.0 is affected.
  4. Is there a fix for CVE-2026-105757? Yes. CVE-2026-105757 is fixed in 0.30.0. Upgrade to this version or later.
  5. Is CVE-2026-105757 exploitable, and should I be worried? Whether CVE-2026-105757 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
  6. What actually determines whether CVE-2026-105757 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
  7. How do I fix CVE-2026-105757? Upgrade vllm to 0.30.0 or later.

Other vulnerabilities in vllm

Stop the waste.
Protect your environment with Kodem.