From 3794b4dd0f7f3d08477c1a5de1e09e0d18999002 Mon Sep 17 00:00:00 2001 From: Vardhman Gupta Date: Sun, 8 Nov 2026 13:58:47 +0530 Subject: [PATCH] FIX: Normalize newlines and tabs in MathObfuscationConverter (#4059) --- pyrit/converter/math_obfuscation_converter.py | 6 ++- .../test_math_obfuscation_converter.py | 48 +++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/pyrit/converter/math_obfuscation_converter.py b/pyrit/converter/math_obfuscation_converter.py index 881b1a200b..746dedb155 100644 --- a/pyrit/converter/math_obfuscation_converter.py +++ b/pyrit/converter/math_obfuscation_converter.py @@ -140,12 +140,14 @@ async def convert_async( lines: list[str] = [] first_equation_added = False - for ch in prompt: + normalized_prompt = prompt.replace("\r\n", "\n").replace("\r", "\n") + + for ch in normalized_prompt: if ch == "\n": lines.append("") continue - if ch == " ": + if ch in (" ", "\t"): lines.append("") continue diff --git a/tests/unit/converter/test_math_obfuscation_converter.py b/tests/unit/converter/test_math_obfuscation_converter.py index 48095f9dd7..2f4011ab0c 100644 --- a/tests/unit/converter/test_math_obfuscation_converter.py +++ b/tests/unit/converter/test_math_obfuscation_converter.py @@ -48,6 +48,54 @@ def test_math_obfuscation_spaces_and_newlines(): assert lines[4].startswith("C =") +def test_math_obfuscation_crlf_and_cr(): + """CRLF and CR should be treated as newlines without generating control character equations.""" + converter = MathObfuscationConverter(rng=random.Random(1), hint="", suffix="") + + result_crlf = asyncio.run(converter.convert_async(prompt="A\r\nB")) + lines_crlf = result_crlf.output_text.splitlines() + assert lines_crlf[0].startswith("A =") + assert lines_crlf[1] == "" + assert lines_crlf[2].startswith("B =") + assert not any(line.startswith("\r =") for line in lines_crlf) + + result_cr = asyncio.run(converter.convert_async(prompt="A\rB")) + lines_cr = result_cr.output_text.splitlines() + assert lines_cr[0].startswith("A =") + assert lines_cr[1] == "" + assert lines_cr[2].startswith("B =") + assert not any(line.startswith("\r =") for line in lines_cr) + + +def test_math_obfuscation_tabs(): + """Horizontal tabs should be treated as whitespace word separators.""" + converter = MathObfuscationConverter(rng=random.Random(1), hint="", suffix="") + + result = asyncio.run(converter.convert_async(prompt="A\tB")) + lines = result.output_text.splitlines() + assert lines[0].startswith("A =") + assert lines[1] == "" + assert lines[2].startswith("B =") + assert not any(line.startswith("\t =") for line in lines) + + +def test_math_obfuscation_mixed_control_whitespace(): + """Mixed tabs, CRLF, and spaces should process cleanly with equations only for letters.""" + converter = MathObfuscationConverter(rng=random.Random(1), hint="", suffix="") + + result = asyncio.run(converter.convert_async(prompt="A\tB\r\nC\rD")) + lines = result.output_text.splitlines() + assert len(lines) == 7 + assert lines[0].startswith("A =") + assert lines[1] == "" + assert lines[2].startswith("B =") + assert lines[3] == "" + assert lines[4].startswith("C =") + assert lines[5] == "" + assert lines[6].startswith("D =") + assert not any(line.startswith(("\r =", "\t =")) for line in lines) + + def test_math_obfuscation_empty_string(): """Empty input should produce empty output.""" converter = MathObfuscationConverter(rng=random.Random(0), hint="", suffix="")