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:
Krrish Dholakia 2026-02-28 17:39:21 -08:00
parent 450a77d6c5
commit 51cdce0883
2 changed files with 38 additions and 7 deletions

View file

@ -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")

View file

@ -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."""