Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion engine/compliance/pii.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,14 @@
PIICategory.EMAIL: re.compile(
r"[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}",
),
# The guards keep a 10-digit run from matching inside a longer alphanumeric
# token — opaque identifiers such as `q_5c9382647927` (handle_match's
# query_id) otherwise read as phone numbers and get redacted out of the
# response. `\b` cannot be used here because a leading `+` or `(` is not a
# word character, so it would break `+1 (555) 123-4567`. SSN and IP_ADDRESS
# below rely on `\b` for the same reason, their patterns start with a digit.
PIICategory.PHONE: re.compile(
r"(?:\+?1[\-\s.]?)?\(?\d{3}\)?[\-\s.]?\d{3}[\-\s.]?\d{4}",
r"(?<![0-9A-Za-z_])(?:\+?1[\-\s.]?)?\(?\d{3}\)?[\-\s.]?\d{3}[\-\s.]?\d{4}(?![0-9A-Za-z_])",

Check warning on line 69 in engine/compliance/pii.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use concise character class syntax '\w' instead of '[0-9A-Za-z_]'.

See more on https://sonarcloud.io/project/issues?id=Quantum-L9_Cognitive.Engine.Graphs&issues=AaC3Tki-IyfdyxyWU0lr&open=AaC3Tki-IyfdyxyWU0lr&pullRequest=271

Check warning on line 69 in engine/compliance/pii.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use concise character class syntax '\w' instead of '[0-9A-Za-z_]'.

See more on https://sonarcloud.io/project/issues?id=Quantum-L9_Cognitive.Engine.Graphs&issues=AaC3Tki-IyfdyxyWU0lq&open=AaC3Tki-IyfdyxyWU0lq&pullRequest=271
),
PIICategory.SSN: re.compile(
r"\b\d{3}[\-\s]?\d{2}[\-\s]?\d{4}\b",
Expand Down
21 changes: 21 additions & 0 deletions tests/compliance/test_hipaa.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,27 @@ def test_detect_ssn_by_pattern(self) -> None:
assert detections[0].category == PIICategory.SSN
assert detections[0].detected_by == "pattern_match"

def test_phone_pattern_does_not_match_inside_opaque_identifier(self) -> None:
"""A digit run inside a longer token is not a phone number.

Regression: handle_match builds query_id as `q_<12 hex chars>`. Roughly
1.6% of those contain a 10-digit run, which the unguarded phone pattern
matched — ComplianceEngine.redact_response then deleted query_id from
the match response for those requests.
"""
handler = PIIHandler()

for opaque in ("q_5c9382647927", "q_a90333567456", "q_6281353751e8"):
assert handler.detect_pii({"query_id": opaque}) == [], f"{opaque} must not be treated as PII"

def test_detect_phone_by_pattern_still_matches_real_numbers(self) -> None:
"""Guarding the phone pattern must not cost real phone detection."""
handler = PIIHandler()

for number in ("555-123-4567", "+1 (555) 123-4567", "5551234567", "call 555.123.4567 now"):
detections = handler.detect_pii({"notes": number})
assert [d.category for d in detections] == [PIICategory.PHONE], f"{number} must be detected as a phone"

def test_detect_nested_pii(self) -> None:
"""PII detected in nested dicts."""
handler = PIIHandler()
Expand Down
7 changes: 6 additions & 1 deletion tests/unit/test_gate_egress.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,12 @@ async def test_request_enrichment_fails_closed_without_gate_url(monkeypatch: pyt
fake = _FakeClient(response=_response_packet())
monkeypatch.setattr(gate_egress, "get_gate_client", lambda: fake)
result = await request_enrichment(tenant="acme", entity_id="ent-1", domain="plasticos", target_fields=["polymer"])
assert result == {"status": "failed", "error": "gate_not_configured", "action": "enrich"}
assert result == {
"status": "failed",
"error": "gate_not_configured",
"action": "enrich",
"idempotency_key": enrichment_idempotency_key("acme", "ent-1", ["polymer"]),
}
assert fake.calls == [], "no direct fallback: nothing may be sent when Gate is not configured"


Expand Down
Loading