mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-09 22:31:41 +00:00
Validate :: name separator with identifier check
Only treat :: as a name::regex separator when the left-hand side is a valid identifier (letters, digits, underscores, hyphens). This prevents regex patterns containing :: (e.g. IPv6 ::ffff:\d+) from being silently corrupted by splitting on ::. Added tests for bare :: in regex and valid name:: prefix. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
450a77d6c5
commit
51cdce0883
2 changed files with 38 additions and 7 deletions
|
|
@ -1016,6 +1016,12 @@ _NESTED_QUANTIFIER_RE = re.compile(
|
|||
r"\([^)]*[*+]\)[*+?]|\([^)]*[*+]\)\{",
|
||||
)
|
||||
|
||||
# A valid pattern name must look like an identifier: letters, digits,
|
||||
# underscores, hyphens — no regex metacharacters. Used to decide whether
|
||||
# a ``::`` in a line is a name separator or part of the regex itself
|
||||
# (e.g. IPv6 patterns like ``::ffff:\d+``).
|
||||
_VALID_PATTERN_NAME_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_-]*$")
|
||||
|
||||
|
||||
def _check_redos_heuristic(pattern: str) -> Optional[str]:
|
||||
"""Return an error message if the pattern looks like it could cause ReDoS."""
|
||||
|
|
@ -1105,15 +1111,20 @@ async def validate_patterns_file(request: Dict[str, str]):
|
|||
)
|
||||
break
|
||||
|
||||
# Parse optional name::regex format (:: avoids conflict with
|
||||
# regex alternation operator |)
|
||||
# Parse optional name::regex format. Only treat :: as a
|
||||
# name separator when the left-hand side is a valid
|
||||
# identifier; otherwise the whole line is the regex
|
||||
# (e.g. ``::ffff:\d+`` for IPv6).
|
||||
name: Optional[str] = None
|
||||
regex_str = stripped
|
||||
if "::" in stripped:
|
||||
parts = stripped.split("::", 1)
|
||||
name = parts[0].strip()
|
||||
regex_str = parts[1].strip()
|
||||
else:
|
||||
name = None
|
||||
regex_str = stripped
|
||||
candidate_name = parts[0].strip()
|
||||
if candidate_name and _VALID_PATTERN_NAME_RE.match(
|
||||
candidate_name
|
||||
):
|
||||
name = candidate_name
|
||||
regex_str = parts[1].strip()
|
||||
|
||||
if not regex_str:
|
||||
errors.append(f"Line {line_num}: empty regex pattern")
|
||||
|
|
|
|||
|
|
@ -1203,6 +1203,26 @@ class TestValidatePatternsFile:
|
|||
assert result["valid"] is False
|
||||
assert "too large" in result.get("error", "").lower()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_patterns_double_colon_in_regex(self):
|
||||
""":: in regex (e.g. IPv6) should not be treated as name separator."""
|
||||
file_content = "::ffff:\\d+"
|
||||
result = await validate_patterns_file({"file_content": file_content})
|
||||
assert result["valid"] is True
|
||||
assert len(result["patterns"]) == 1
|
||||
assert result["patterns"][0]["pattern"] == "::ffff:\\d+"
|
||||
assert result["patterns"][0]["name"] == "pattern_line_1"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_patterns_named_with_double_colon(self):
|
||||
"""Valid identifier before :: is treated as name, rest as regex."""
|
||||
file_content = "ipv6_match::fe80::\\w+"
|
||||
result = await validate_patterns_file({"file_content": file_content})
|
||||
assert result["valid"] is True
|
||||
assert len(result["patterns"]) == 1
|
||||
assert result["patterns"][0]["name"] == "ipv6_match"
|
||||
assert result["patterns"][0]["pattern"] == "fe80::\\w+"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_patterns_only_comments(self):
|
||||
"""File with only comments returns no patterns error."""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue