Skip to content

Commit 2dd5134

Browse files
committed
fix: stop no-dash SSN branch matching digit runs inside hex IDs
A bare nine-digit run embedded in a longer alphanumeric token (random hex IDs, UUID segments) matched the SSN pattern because its boundaries only excluded adjacent digits. In practice this let randomly generated server IDs trip the Claude Code PII firewall hook and block entire sessions. Tighten the no-dash branch only: the run must not be followed by a letter, and must start at a non-alphanumeric boundary or right after a two-letter token prefix (preserving v4.4.0 country-code parity, e.g. DE123456789). The dashed branch keeps its existing boundaries.
1 parent 60fa5fd commit 2dd5134

2 files changed

Lines changed: 33 additions & 2 deletions

File tree

datafog/processing/text_processing/regex_annotator/regex_annotator.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,15 +104,23 @@ def __init__(
104104
# inside a DE_VAT_ID) are resolved by the engine's span-overlap
105105
# suppression, not here, so default (EN) detection keeps v4.4.0
106106
# behavior even when German labels are active.
107+
# The no-dash branch has stricter boundaries than the dashed one:
108+
# a nine-digit run embedded in a longer alphanumeric token (random
109+
# hex IDs, UUID segments) is not an SSN. The run must not be
110+
# followed by a letter, and must start either at a non-alphanumeric
111+
# boundary or right after a two-letter token prefix (country codes
112+
# like "DE123456789" — v4.4.0 parity).
107113
"SSN": re.compile(
108114
r"""
109-
(?<!\d)
110115
(?:
116+
(?<!\d)
111117
(?!000|666)\d{3}-(?!00)\d{2}-(?!0000)\d{4}
118+
(?!\d)
112119
|
120+
(?:(?<![0-9A-Za-z])|(?<=\b[A-Za-z]{2}))
113121
(?!000|666)\d{3}(?!00)\d{2}(?!0000)\d{4}
122+
(?![0-9A-Za-z])
114123
)
115-
(?!\d)
116124
""",
117125
re.IGNORECASE | re.MULTILINE | re.VERBOSE,
118126
),

tests/test_regex_annotator.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,29 @@ def test_annotation_result_format():
373373
assert ssn_spans[0].text == "123-45-6789"
374374

375375

376+
def test_ssn_no_dash_not_flagged_inside_alphanumeric_tokens():
377+
"""Regression guard: bare nine-digit runs embedded in longer
378+
alphanumeric tokens (random hex IDs, UUID segments, blob names) must
379+
not match SSN. A digit run counts as embedded when the token
380+
continues with letters after it, or when it is preceded by a token
381+
prefix that is not a bare two-letter country code (see
382+
test_ssn_detection_keeps_v44_behavior_for_country_prefixed_digits)."""
383+
annotator = RegexAnnotator()
384+
for text in (
385+
"serverId 3f2a123456789bc is up.", # letters on both sides
386+
"session 9c-3f2a123456789-77 restarted.", # run ends a hex segment
387+
"blob deadbeef123456789cafe stored.",
388+
"id a1b2-123456789abc-x9", # run starts a segment, letters after
389+
):
390+
assert annotator.annotate(text)["SSN"] == [], text
391+
392+
393+
def test_ssn_no_dash_still_flagged_standalone():
394+
"""A bare nine-digit number that is its own token must keep matching."""
395+
annotator = RegexAnnotator()
396+
assert annotator.annotate("Case 219099999 assigned.")["SSN"] == ["219099999"]
397+
398+
376399
def test_ssn_detection_keeps_v44_behavior_for_country_prefixed_digits():
377400
"""Regression guard: bare nine-digit runs after a country prefix must
378401
still match SSN when no locale is configured (v4.4.0 parity). The

0 commit comments

Comments
 (0)