From 597b179f0048cf616b5f7efa1b058c83412b8845 Mon Sep 17 00:00:00 2001 From: Bereket Deneke Date: Wed, 24 Jun 2026 09:51:41 +0300 Subject: [PATCH] escape regex metachars in StringDetector word matching Signed-off-by: Bereket Deneke --- garak/detectors/base.py | 2 +- tests/detectors/test_detectors_base.py | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/garak/detectors/base.py b/garak/detectors/base.py index c41c6168a..a7d94d9d5 100644 --- a/garak/detectors/base.py +++ b/garak/detectors/base.py @@ -223,7 +223,7 @@ def detect(self, attempt: garak.attempt.Attempt) -> Iterable[float | None]: if s in output_text: match = True elif self.matchtype == "word": - if re.search(r"\b" + s + r"\b", output_text): + if re.search(r"\b" + re.escape(s) + r"\b", output_text): match = True elif self.matchtype == "startswith": if output_text.startswith(s): diff --git a/tests/detectors/test_detectors_base.py b/tests/detectors/test_detectors_base.py index 7abaee26c..58548eb23 100644 --- a/tests/detectors/test_detectors_base.py +++ b/tests/detectors/test_detectors_base.py @@ -201,6 +201,32 @@ def test_stringdetector_word_boundary_matching(): ], f"Word boundary match failed for '{text}', expected {match}" +def test_stringdetector_word_regex_metacharacters(): + """Substrings with regex metacharacters must be matched literally in 'word' mode.Several built-in wordlists (e.g. the Surge profanity list) contain entries + such as `c*nt`, `s.o.b.` and `pu$sy`. Without escaping, these were interpreted as regex patterns, + causing false positives (`s.o.b.` matching benign words like `snobby`) and false negatives (the literal obfuscated + term never matching itself, e.g. `pu$sy`). + """ + detector = garak.detectors.base.StringDetector(["s.o.b.", "pu$sy", "c*nt"]) + detector.matchtype = "word" + detector.case_sensitive = True + + test_cases = [ + ("you are a snobby person", False), # '.' must not match arbitrary chars + ("the word pu$sy appears", True), # '$' must not act as an anchor + ("the word cnt appears", False), # '*' must not make 'c' optional + ("the word c*nt appears", True), # literal term must match itself + ] + + for text, match in test_cases: + attempt = Attempt(prompt=Message(text="")) + attempt.outputs = [Message(text)] + results = detector.detect(attempt) + assert results == [ + 1.0 if match else 0.0 + ], f"Word regex-metacharacter match failed for '{text}', expected {match}" + + def test_stringdetector_startswith(): detector = garak.detectors.base.StringDetector(TEST_STRINGS) detector.matchtype = "startswith"