Skip to content
22 changes: 11 additions & 11 deletions appguardrail_core/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,11 @@ def as_dict(self) -> dict[str, Any]:

def extract_public_references(message: str) -> tuple[str, ...]:
"""Extract OWASP, CWE, and CVE references already embedded in rule copy."""
return tuple(
dict.fromkeys(
" ".join(match.group(1).split())
for match in REFERENCE_RE.finditer(message or "")
)
)
seen = {}
if message:
for match in REFERENCE_RE.finditer(message):
seen[" ".join(match.group(1).split())] = None
return tuple(seen)


def _category_for_references(references: tuple[str, ...], fallback: str) -> str:
Expand Down Expand Up @@ -174,8 +173,9 @@ def validate_rule_metadata(metadata: RuleMetadata | dict[str, Any]) -> list[str]


def _merge_references(*groups: tuple[str, ...]) -> tuple[str, ...]:
return tuple(
dict.fromkeys(
reference for group in groups for reference in group if reference
)
)
seen = {}
for group in groups:
for reference in group:
if reference:
seen[reference] = None
return tuple(seen)
36 changes: 36 additions & 0 deletions tests/test_reference_deduplication_contract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""Regression contracts for reference extraction and ordered deduplication."""

from appguardrail_core.rules import _merge_references, extract_public_references


def test_extract_public_references_preserves_first_seen_order_and_normalizes_space() -> None:
"""Repeated public references keep first-seen order after whitespace normalization."""
message = (
"Finding [CWE-918 - Server-Side Request Forgery] then "
"[OWASP A10:2021 - Server-Side Request Forgery], then "
"[CWE-918 - Server-Side Request Forgery] again."
)

assert extract_public_references(message) == (
"CWE-918 - Server-Side Request Forgery",
"OWASP A10:2021 - Server-Side Request Forgery",
)


def test_extract_public_references_handles_empty_and_unmatched_messages() -> None:
"""Messages without supported taxonomy references return an empty tuple."""
assert extract_public_references("") == ()
assert extract_public_references("plain finding without a public reference") == ()


def test_merge_references_preserves_order_while_dropping_empty_and_duplicate_values() -> None:
"""Merging groups keeps the first occurrence and omits empty references."""
assert _merge_references(
("CWE-918 - Server-Side Request Forgery", "", "CWE-74 - Injection"),
("CWE-918 - Server-Side Request Forgery", "OWASP A03:2021 - Injection"),
(),
) == (
"CWE-918 - Server-Side Request Forgery",
"CWE-74 - Injection",
"OWASP A03:2021 - Injection",
)
Loading