CVE case study
CVE-2026-73556: An Unguarded Regex Compile Let One Request Stall vLLM's Engine
vLLM's lm-format-enforcer structured-output backend compiled an attacker-supplied regex with no timeout, so one catastrophic pattern pegged a CPU core and stalled the engine worker. Version 0.26.0 routes the compile through the same timeout guard the sibling backends already used.
- Weakness
- CWE-400, CWE-1333
- Affected
- vLLM 0.24.0 and earlier
- Remediation state
- Upgrade to vLLM 0.26.0 or later
- Advisory published
- 25 Jul 2026
Official vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
Why it matters
vLLM builds a grammar for each structured-output request from a client-supplied schema. The lm-format-enforcer backend accepted a structured_outputs.regex value and compiled it into an interegular finite-state machine before generation began. That compile ran synchronously inside the engine's structured-output path.
A prior advisory, GHSA-rwxx-mrjm-wc2m, had already fixed the same class of issue in the xgrammar and outlines backends by wrapping the compile in compile_regex_with_timeout. The lm-format-enforcer backend was left without that guard and without a buildability check, so a single catastrophic pattern such as (a{1,300}){300} could hang the compile step.
vLLM ships with no authentication by default, so the request needs no credentials. The public record scores it Moderate at 5.3 because the operator must first select the lm-format-enforcer backend, the same opt-in tier as the outlines backend the earlier advisory already covered.
How I found it
On 4 July 2026, I was reading vLLM's structured-output backends after the fix for GHSA-rwxx-mrjm-wc2m, which wrapped the regex compile in the xgrammar and outlines backends with compile_regex_with_timeout. I checked whether every backend that compiles a user-supplied regex received the same guard.
The lm-format-enforcer backend did not. It built lmformatenforcer.RegexParser(grammar_spec) synchronously with no timeout, and its request validator returned as soon as it saw a regex present, with no buildability or size check. That is the same interegular FSM-construction primitive the earlier advisory cited for the outlines backend.
I confirmed the sink by timing the exact call the backend makes against a synthetic catastrophic pattern in a disposable local environment. I sent no request to a running server and used no model.
The maintainer accepted the report the same day, scored it Moderate to match the sibling advisory's non-default-backend reachability, and merged a fix that routes the compile through the shared timeout guard and rejects un-buildable patterns.
Root cause
backend_lm_format_enforcer.py built lmformatenforcer.RegexParser(grammar_spec) directly. That call constructs an interegular FSM from the supplied pattern with no time limit, so a pattern with heavy nested quantifiers spends unbounded time in FSM construction.
validate_structured_output_request_lm_format_enforcer() returned as soon as it saw a regex present. It ran no bounded trial compile and no size or buildability check, so the validator could not reject a catastrophic pattern before it reached the sink.
The sibling backends compiled the same untrusted regex through compile_regex_with_timeout, and outlines also ran validate_regex_is_buildable. The defect was a missing sibling guard: one backend in the same subsystem never received the fix applied to the others.
Source-to-sink trace
- 01Untrusted entry
POST /v1/completions structured_outputs.regexvLLM ships with no authentication by default, so any client could supply the regex used to build the grammar for a request.
- 02Validation gap
validate_structured_output_request_lm_format_enforcer()The validator returned as soon as it saw a regex field. It ran no bounded compile and no buildability check, so a catastrophic pattern passed straight through.
- 03Compile sink
backend_lm_format_enforcer.py, RegexParser(grammar_spec)The backend built the interegular FSM synchronously in the engine's structured-output path with no timeout. One catastrophic pattern pinned a CPU core and blocked that path.
- 04Guarded siblings
backend_xgrammar.py and backend_outlines.pyBoth already wrapped the same compile in compile_regex_with_timeout, and outlines also ran validate_regex_is_buildable. Only the lm-format-enforcer backend was left unguarded.
- 05Fixed decision
vLLM 0.26.0 lm-format-enforcer backendThe fix routes the RegexParser build through compile_regex_with_timeout and rejects un-compilable or oversized patterns in the validator with a clean error.
Safe proof of concept
Prerequisites
- Python 3 on a local, disposable machine.
- Optional: the lm-format-enforcer package (or interegular) installed to exercise the real sink. The model below falls back to interegular, which builds the same FSM.
- No GPU, model weights, running vLLM server, or network access.
Step-by-step reproduction
- Read the affected lm-format-enforcer backend and confirm that RegexParser is built with no timeout and that the request validator returns without a bounded compile.
- Confirm that the xgrammar and outlines backends already route the same compile through compile_regex_with_timeout, so this is a missing sibling guard rather than a new class of bug.
- Run the bounded model below. It builds the FSM inside a worker thread with a hard deadline, so a catastrophic pattern shows up as a missed deadline instead of a hung terminal.
- Compare the baseline pattern, which finishes in well under a second, against the synthetic catastrophic pattern, which does not finish before the deadline while one core stays at full use.
- On 0.26.0, repeat the same call and confirm the compile now raises a bounded error instead of building without limit.
Locate the missing guard in a local affected checkout
# Offline source review only. Point SRC at a local checkout of an
# affected vLLM (0.24.0 or earlier). No network call, no server, no model.
SRC=vllm/v1/structured_output
echo "Backends that route the regex compile through the timeout guard:"
grep -rl "compile_regex_with_timeout" \
"$SRC/backend_xgrammar.py" "$SRC/backend_outlines.py"
echo "The lm-format-enforcer backend on affected versions has no such guard:"
grep -n "compile_regex_with_timeout" "$SRC/backend_lm_format_enforcer.py" \
|| echo " guard absent: RegexParser(grammar_spec) is built with no timeout"Bounded model of the unguarded compile sink
# CVE-2026-73556: the lm-format-enforcer backend compiled a user regex with
# no timeout. This models the exact sink safely: it builds the same FSM inside
# a worker thread with a hard deadline, so the demonstration cannot hang.
# No network, no model, no vllm serve. Synthetic pattern only.
import threading
import time
def build(regex):
# The backend calls lmformatenforcer.RegexParser(regex), which builds an
# interegular FSM. Prefer the real sink; fall back to interegular directly.
try:
import lmformatenforcer
lmformatenforcer.RegexParser(regex)
except ImportError:
import interegular
interegular.parse_pattern(regex).to_fsm()
def timed_build(regex, deadline_s):
done = threading.Event()
# daemon thread: a stuck compile cannot block interpreter exit
t = threading.Thread(target=lambda: (build(regex), done.set()), daemon=True)
start = time.perf_counter()
t.start()
finished = done.wait(deadline_s)
return finished, time.perf_counter() - start
BASELINE = "[0-9]{3}"
CATASTROPHIC = "(a{1,300}){300}" # synthetic; never sent to a real endpoint
ok, secs = timed_build(BASELINE, 5)
print(f"baseline {BASELINE!r:18} finished={ok} in {secs:.4f}s")
ok, secs = timed_build(CATASTROPHIC, 5)
print(f"catastrophic {CATASTROPHIC!r:18} finished={ok} after {secs:.1f}s deadline")Expected evidence
- The baseline pattern builds in well under a second.
- The catastrophic pattern does not finish before the five-second deadline, and one CPU core stays at full use in FSM construction.
- On an affected vLLM, the same pattern sent to the lm-format-enforcer backend never returns and blocks concurrent structured-output requests, which is the worker-level denial of service.
The identical request against the xgrammar or outlines backend is bounded by compile_regex_with_timeout and returns a clean error instead of hanging. The baseline pattern also finishes at once on every backend.
On vLLM 0.26.0, the lm-format-enforcer backend routes the RegexParser build through compile_regex_with_timeout and rejects the pattern in the validator. The synthetic pattern now raises a bounded error rather than pinning a core.
Impact
A single unauthenticated request could pin a CPU core in FSM construction and never return. Because the compile runs in the engine's structured-output path, concurrent requests that need that path stall behind it, which is a worker-level denial of service.
The official vector records no confidentiality or integrity impact and low availability impact. Reaching the sink requires the operator to have selected the lm-format-enforcer backend through the structured-outputs configuration; the default backend is auto, which resolves to xgrammar.
Fix and retest
Upgrade to vLLM 0.26.0 or later. The fix routes the lm-format-enforcer regex compile through compile_regex_with_timeout, the same guard the xgrammar and outlines backends already used.
The validator now runs a bounded compile and rejects un-compilable or oversized patterns with a clean error, so a catastrophic pattern is refused before it can reach the synchronous build.
If an upgrade is not yet possible, keep the default auto backend rather than selecting lm-format-enforcer, and place the inference endpoint behind authentication and request limits so an anonymous client cannot submit arbitrary grammars.
Engineering lesson
When a fix guards one code path, inventory every sibling that reaches the same primitive. Here the interegular FSM builder was called from three backends, and a fix that named two of them left the third exposed to the same input.
Compile untrusted patterns under a deadline and a size limit, and reject them at validation. A structured-output feature should never let one request hold a synchronous engine step open without a bound.