From 89ce6672d9f65f8b0bda2950b5e64732c7006486 Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Thu, 3 Sep 2026 19:41:25 +0100 Subject: [PATCH 1/3] FIX: validate VariationSelector base character --- .../variation_selector_smuggler_converter.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pyrit/converter/token_smuggling/variation_selector_smuggler_converter.py b/pyrit/converter/token_smuggling/variation_selector_smuggler_converter.py index ad2ecd1a57..11b68eed32 100644 --- a/pyrit/converter/token_smuggling/variation_selector_smuggler_converter.py +++ b/pyrit/converter/token_smuggling/variation_selector_smuggler_converter.py @@ -47,10 +47,13 @@ def __init__( Default is True. Raises: - ValueError: If an unsupported action or ``encoding_mode`` is provided. + ValueError: If an unsupported action is provided or ``base_char_utf8`` is not exactly one character. """ super().__init__(action=action) - self.utf8_base_char = base_char_utf8 if base_char_utf8 is not None else "😊" + base_char = base_char_utf8 if base_char_utf8 is not None else "😊" + if len(base_char) != 1: + raise ValueError("base_char_utf8 must be exactly one character.") + self.utf8_base_char = base_char self.embed_in_base = embed_in_base def _build_identifier(self) -> ComponentIdentifier: From d3f9a2514c8280ecfb90a9255c4029ddab4824eb Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Thu, 3 Sep 2026 19:41:39 +0100 Subject: [PATCH 2/3] TEST: cover VariationSelector base character validation --- ...est_variation_selector_smuggler_converter.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/unit/converter/test_variation_selector_smuggler_converter.py b/tests/unit/converter/test_variation_selector_smuggler_converter.py index 59e242093f..4bd7cb0f9d 100644 --- a/tests/unit/converter/test_variation_selector_smuggler_converter.py +++ b/tests/unit/converter/test_variation_selector_smuggler_converter.py @@ -43,6 +43,23 @@ def test_variation_selector_invalid_action(): VariationSelectorSmugglerConverter(action="invalid") +@pytest.mark.parametrize("base_char", ["", "ab", "😊x"]) +def test_variation_selector_invalid_base_char(base_char): + with pytest.raises(ValueError, match="base_char_utf8 must be exactly one character"): + VariationSelectorSmugglerConverter(base_char_utf8=base_char) + + +async def test_variation_selector_custom_base_char_roundtrip(): + encoder = VariationSelectorSmugglerConverter(action="encode", base_char_utf8="A") + encoded = await encoder.convert_async(prompt="test", input_type="text") + + decoder = VariationSelectorSmugglerConverter(action="decode", base_char_utf8="A") + decoded = await decoder.convert_async(prompt=encoded.output_text, input_type="text") + + assert encoded.output_text.startswith("A") + assert decoded.output_text == "test" + + async def test_variation_selector_input_not_supported(): converter = VariationSelectorSmugglerConverter(action="encode") with pytest.raises(ValueError, match="Input type not supported"): From 2d45d6a61f25e6a52b084ae008544d14f0c9dc16 Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Thu, 10 Sep 2026 12:23:39 +0100 Subject: [PATCH 3/3] fix: reject variation selectors as smuggler base characters Signed-off-by: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> --- .../variation_selector_smuggler_converter.py | 6 +++++- ...st_variation_selector_smuggler_converter.py | 18 +++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/pyrit/converter/token_smuggling/variation_selector_smuggler_converter.py b/pyrit/converter/token_smuggling/variation_selector_smuggler_converter.py index 11b68eed32..5b874a98ce 100644 --- a/pyrit/converter/token_smuggling/variation_selector_smuggler_converter.py +++ b/pyrit/converter/token_smuggling/variation_selector_smuggler_converter.py @@ -47,12 +47,16 @@ def __init__( Default is True. Raises: - ValueError: If an unsupported action is provided or ``base_char_utf8`` is not exactly one character. + ValueError: If an unsupported action is provided, or ``base_char_utf8`` is not exactly one character + or is a variation selector used to encode payload bytes. """ super().__init__(action=action) base_char = base_char_utf8 if base_char_utf8 is not None else "😊" if len(base_char) != 1: raise ValueError("base_char_utf8 must be exactly one character.") + code_point = ord(base_char) + if 0xFE00 <= code_point <= 0xFE0F or 0xE0100 <= code_point <= 0xE01EF: + raise ValueError("base_char_utf8 must not be a variation selector.") self.utf8_base_char = base_char self.embed_in_base = embed_in_base diff --git a/tests/unit/converter/test_variation_selector_smuggler_converter.py b/tests/unit/converter/test_variation_selector_smuggler_converter.py index 4bd7cb0f9d..527fda532b 100644 --- a/tests/unit/converter/test_variation_selector_smuggler_converter.py +++ b/tests/unit/converter/test_variation_selector_smuggler_converter.py @@ -44,19 +44,27 @@ def test_variation_selector_invalid_action(): @pytest.mark.parametrize("base_char", ["", "ab", "😊x"]) -def test_variation_selector_invalid_base_char(base_char): +def test_variation_selector_invalid_base_char(base_char: str) -> None: with pytest.raises(ValueError, match="base_char_utf8 must be exactly one character"): VariationSelectorSmugglerConverter(base_char_utf8=base_char) -async def test_variation_selector_custom_base_char_roundtrip(): - encoder = VariationSelectorSmugglerConverter(action="encode", base_char_utf8="A") +@pytest.mark.parametrize("base_char", ["\ufe00", "\ufe0f", "\U000e0100", "\U000e01ef"]) +def test_variation_selector_rejects_selector_base_char(base_char: str) -> None: + with pytest.raises(ValueError, match="base_char_utf8 must not be a variation selector"): + VariationSelectorSmugglerConverter(base_char_utf8=base_char) + + +@pytest.mark.parametrize("base_char", ["A", "\ufdff", "\ufe10", "\U000e00ff", "\U000e01f0"]) +@pytest.mark.parametrize("embed_in_base", [True, False]) +async def test_variation_selector_custom_base_char_roundtrip(base_char: str, embed_in_base: bool) -> None: + encoder = VariationSelectorSmugglerConverter(action="encode", base_char_utf8=base_char, embed_in_base=embed_in_base) encoded = await encoder.convert_async(prompt="test", input_type="text") - decoder = VariationSelectorSmugglerConverter(action="decode", base_char_utf8="A") + decoder = VariationSelectorSmugglerConverter(action="decode", base_char_utf8=base_char, embed_in_base=embed_in_base) decoded = await decoder.convert_async(prompt=encoded.output_text, input_type="text") - assert encoded.output_text.startswith("A") + assert encoded.output_text.startswith(base_char) assert decoded.output_text == "test"