From b2e61abde747029c150d042c62c52e62a182e47a Mon Sep 17 00:00:00 2001 From: umerkhan Date: Tue, 8 Sep 2026 00:50:50 +0530 Subject: [PATCH] FIX: validate BinaryConverter bit width per converted word BinaryConverter.validate_input inspected the entire prompt, but WordLevelConverter.convert_async validates before applying the word selection strategy. A character in an unselected word therefore failed the conversion even though that word is passed through unencoded and cannot overflow bits_per_char. Move the check to the words that are actually converted. The default all-words path is unchanged, so this only affects prompts where a selection strategy leaves the offending word untouched. Rename the helper to _validate_word to match the style guide, which marks internal validation helpers as private. Co-Authored-By: Claude Opus 5 (1M context) --- pyrit/converter/binary_converter.py | 17 ++++++++++----- tests/unit/converter/test_binary_converter.py | 21 +++++++++++++++++++ 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/pyrit/converter/binary_converter.py b/pyrit/converter/binary_converter.py index 73e5a1e133..bbadaabfd8 100644 --- a/pyrit/converter/binary_converter.py +++ b/pyrit/converter/binary_converter.py @@ -64,18 +64,18 @@ def _build_identifier(self) -> ComponentIdentifier: } ) - def validate_input(self, prompt: str) -> None: + def _validate_word(self, word: str) -> None: """ - Check if ``bits_per_char`` is sufficient for the characters in the prompt. + Check if ``bits_per_char`` is sufficient for the characters in a word being converted. Args: - prompt (str): The input text prompt to validate. + word (str): The word that is about to be converted. Raises: - ValueError: If ``bits_per_char`` is too small to represent any character in the prompt. + ValueError: If ``bits_per_char`` is too small to represent any character in the word. """ bits = self.bits_per_char.value - max_code_point = max((ord(char) for char in prompt), default=0) + max_code_point = max((ord(char) for char in word), default=0) min_bits_required = max_code_point.bit_length() if bits < min_bits_required: raise ValueError( @@ -92,7 +92,14 @@ async def convert_word_async(self, word: str) -> str: Returns: str: The converted word. + + Raises: + ValueError: If ``bits_per_char`` is too small to represent any character in the word. """ + # Validated per word rather than over the whole prompt: a word selection strategy may + # leave words untouched, and a character that is never encoded cannot overflow + # bits_per_char. + self._validate_word(word) bits = self.bits_per_char.value return " ".join(format(ord(char), f"0{bits}b") for char in word) diff --git a/tests/unit/converter/test_binary_converter.py b/tests/unit/converter/test_binary_converter.py index 41bfc3da14..c21e7e19de 100644 --- a/tests/unit/converter/test_binary_converter.py +++ b/tests/unit/converter/test_binary_converter.py @@ -4,6 +4,7 @@ import pytest from pyrit.converter import BinaryConverter, ConverterResult +from pyrit.converter.text_selection_strategy import WordIndexSelectionStrategy async def test_binary_converter_8_bit_ascii(): @@ -38,3 +39,23 @@ async def test_binary_converter_32_bit_emoji(): async def test_binary_converter_invalid_bits_per_char(): with pytest.raises(TypeError, match="bits_per_char must be an instance of BinaryConverter.BitsPerChar Enum."): BinaryConverter(bits_per_char=10) # Invalid bits_per_char + + +async def test_binary_converter_raises_when_selected_word_exceeds_bits(): + converter = BinaryConverter(bits_per_char=BinaryConverter.BitsPerChar.BITS_16) + with pytest.raises(ValueError, match="bits_per_char=16 is too small"): + await converter.convert_async(prompt="hello 👋", input_type="text") + + +async def test_binary_converter_ignores_unselected_word_exceeding_bits(): + # Only "hello" is converted, so the emoji in the unselected word is passed + # through untouched and must not fail validation. + converter = BinaryConverter( + bits_per_char=BinaryConverter.BitsPerChar.BITS_16, + word_selection_strategy=WordIndexSelectionStrategy(indices=[0]), + ) + result = await converter.convert_async(prompt="hello 👋", input_type="text") + expected_hello = " ".join(format(ord(char), "016b") for char in "hello") + space_binary = format(ord(" "), "016b") + assert result.output_text == f"{expected_hello} {space_binary} 👋" + assert result.output_type == "text"