[Bugfix] Reject NUL byte in structured_outputs.regex (#51796)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
f80b66f548
commit
38f097fab8
@@ -70,3 +70,36 @@ def test_degenerate_structured_outputs_rejected(structured_outputs, match):
|
||||
StructuredOutputsConfig(),
|
||||
tokenizer=object(),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"regex",
|
||||
[
|
||||
"\x00", # a lone leading NUL
|
||||
"\x00\x01\x02\x1f", # a NUL followed by other control chars
|
||||
"[0-9]\x00", # an embedded NUL
|
||||
],
|
||||
)
|
||||
def test_regex_with_nul_byte_rejected(regex):
|
||||
"""A NUL byte is never meaningful in a structured-outputs regex and is not
|
||||
handled by xgrammar's native regex converter. It must be rejected at request
|
||||
validation in every backend mode (a clean 400), instead of reaching that
|
||||
native code or silently falling back to another backend in the default
|
||||
'auto' mode."""
|
||||
params = SamplingParams(structured_outputs=StructuredOutputsParams(regex=regex))
|
||||
|
||||
# Rejected before backend selection, so it is a 400 even in 'auto' mode
|
||||
# (which would otherwise catch the error and fall back to another backend).
|
||||
with pytest.raises(VLLMValidationError, match="NUL"):
|
||||
params._validate_structured_outputs(
|
||||
_StubModelConfig(is_diffusion=False),
|
||||
StructuredOutputsConfig(),
|
||||
tokenizer=object(),
|
||||
)
|
||||
|
||||
# The xgrammar backend also rejects it directly (defense in depth), before
|
||||
# the pattern reaches the native from_regex call.
|
||||
from vllm.v1.structured_output.backend_xgrammar import validate_xgrammar_grammar
|
||||
|
||||
with pytest.raises(ValueError, match="NUL"):
|
||||
validate_xgrammar_grammar(params)
|
||||
|
||||
@@ -1029,6 +1029,15 @@ class SamplingParams(
|
||||
"structured_outputs.json_object must be True if set; omit "
|
||||
"structured_outputs to disable structured outputs"
|
||||
)
|
||||
# Reject a regex containing a NUL byte early, in every backend mode. A
|
||||
# NUL is never meaningful in a regex pattern and is not handled by the
|
||||
# regex-to-grammar conversion. Checked here, before backend selection,
|
||||
# so it is a clean 400 rather than a silent fallback in the default
|
||||
# "auto" mode.
|
||||
if self.structured_outputs.regex and "\x00" in self.structured_outputs.regex:
|
||||
raise VLLMValidationError(
|
||||
"structured_outputs.regex must not contain a NUL character ('\\x00')"
|
||||
)
|
||||
|
||||
from vllm.v1.structured_output.backend_guidance import (
|
||||
has_guidance_unsupported_json_features,
|
||||
|
||||
@@ -284,6 +284,14 @@ def validate_xgrammar_grammar(sampling_params: SamplingParams) -> None:
|
||||
so_params = sampling_params.structured_outputs
|
||||
|
||||
if so_params.regex:
|
||||
# A NUL byte is never meaningful in a regex pattern and is not handled
|
||||
# by xgrammar's native regex converter. Reject it here, before the
|
||||
# pattern reaches that native code; the try/except below does not cover
|
||||
# this case.
|
||||
if "\x00" in so_params.regex:
|
||||
raise ValueError(
|
||||
"structured_outputs.regex must not contain a NUL character ('\\x00')"
|
||||
)
|
||||
try:
|
||||
compile_regex_with_timeout(
|
||||
xgr.Grammar.from_regex,
|
||||
|
||||
Reference in New Issue
Block a user