From e8b6967588b7e76120564b29e1acbf9a9c35205f Mon Sep 17 00:00:00 2001 From: Hrushikesh Ramilla Date: Wed, 4 Nov 2026 10:59:58 +0530 Subject: [PATCH 1/2] FIX: Include ordered parameter names in TemplateSegmentConverter identifier (#3902) TemplateSegmentConverter assigns prompt segments based on the ordered parameters list in SeedPrompt. Include the ordered parameter names in the converter's identifier params so that differing parameter orderings produce distinct identifier hashes and default registry names, avoiding registration collisions. Signed-off-by: Hrushikesh Ramilla --- pyrit/converter/template_segment_converter.py | 8 ++- .../test_template_segment_converter.py | 58 +++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/pyrit/converter/template_segment_converter.py b/pyrit/converter/template_segment_converter.py index 7140c51e09..77d48470d4 100644 --- a/pyrit/converter/template_segment_converter.py +++ b/pyrit/converter/template_segment_converter.py @@ -18,6 +18,11 @@ class TemplateSegmentConverter(Converter): This converter is a generalized version of this: https://adversa.ai/blog/universal-llm-jailbreak-chatgpt-gpt-4-bard-bing-anthropic-and-beyond/ + + The converter identifier captures the ordered template parameter names alongside + the template hash, parameter count, and seed. Changing parameter ordering alters + segment binding, so differing parameter orders produce distinct identifier hashes + and registry names. """ SUPPORTED_INPUT_TYPES = ("text",) @@ -73,7 +78,7 @@ def __init__( def _build_identifier(self) -> ComponentIdentifier: """ - Build identifier with template parameters. + Build identifier with template hash, ordered parameter names, parameter count, and seed. Returns: ComponentIdentifier: The identifier for this converter. @@ -82,6 +87,7 @@ def _build_identifier(self) -> ComponentIdentifier: return self._create_identifier( params={ "template_hash": template_hash, + "parameters": list(self.prompt_template.parameters or []), "number_parameters": self._number_parameters, "seed": self._seed, } diff --git a/tests/unit/converter/test_template_segment_converter.py b/tests/unit/converter/test_template_segment_converter.py index 73b85b5b9b..f99b252c29 100644 --- a/tests/unit/converter/test_template_segment_converter.py +++ b/tests/unit/converter/test_template_segment_converter.py @@ -6,6 +6,7 @@ from pyrit.converter import TemplateSegmentConverter from pyrit.models import SeedPrompt +from pyrit.registry import ConverterRegistry def test_template_segment_converter_init_default(): @@ -168,3 +169,60 @@ async def test_template_segment_converter_invalid_input_type(): converter = TemplateSegmentConverter() with pytest.raises(ValueError, match="Input type not supported"): await converter.convert_async(prompt="test", input_type="image_path") + + +def test_template_segment_converter_identifier_includes_ordered_parameters(): + """Test that the converter identifier includes ordered parameter names.""" + template = SeedPrompt(value="{{ left }} and {{ right }}", parameters=["left", "right"]) + converter = TemplateSegmentConverter(prompt_template=template, seed=42) + identifier = converter.get_identifier() + + assert identifier.params["parameters"] == ["left", "right"] + assert identifier.params["number_parameters"] == 2 + assert identifier.params["seed"] == 42 + assert "template_hash" in identifier.params + + +def test_template_segment_converter_identifier_differs_when_parameters_reordered(): + """Reordering the template parameters changes binding behavior and must yield distinct hashes.""" + template = "LEFT[{{left}}] RIGHT[{{right}}]" + first = TemplateSegmentConverter( + prompt_template=SeedPrompt(value=template, parameters=["left", "right"]), + seed=7, + ) + second = TemplateSegmentConverter( + prompt_template=SeedPrompt(value=template, parameters=["right", "left"]), + seed=7, + ) + + assert first.get_identifier().hash != second.get_identifier().hash + assert first.get_identifier().params["parameters"] == ["left", "right"] + assert second.get_identifier().params["parameters"] == ["right", "left"] + + +async def test_template_segment_converter_reordered_parameters_coexist_in_registry(): + """Distinct parameter bindings have distinct default registry names and register without conflict.""" + registry = ConverterRegistry() + template = "LEFT[{{left}}] RIGHT[{{right}}]" + first = registry.create_named_instance( + name="source", + type_name="TemplateSegmentConverter", + params={ + "prompt_template": SeedPrompt(value=template, parameters=["left", "right"]), + "seed": 7, + }, + ) + second = registry.recreate_instance( + source=first, + params={"prompt_template": SeedPrompt(value=template, parameters=["right", "left"])}, + ) + + res_first = await first.convert_async(prompt="alpha beta") + res_second = await second.convert_async(prompt="alpha beta") + assert res_first.output_text == "LEFT[alpha] RIGHT[beta]" + assert res_second.output_text == "LEFT[beta] RIGHT[alpha]" + + automatic_names = ConverterRegistry() + automatic_names.instances.register(first) + automatic_names.instances.register(second) + assert len(automatic_names.instances) == 2 From 353d01dc95cf89a724b72016c22cbdc44369c729 Mon Sep 17 00:00:00 2001 From: Hrushikesh Ramilla Date: Wed, 4 Nov 2026 11:03:58 +0530 Subject: [PATCH 2/2] test(converter): append _async suffix to registry coexistence test coroutine Signed-off-by: Hrushikesh Ramilla --- tests/unit/converter/test_template_segment_converter.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/converter/test_template_segment_converter.py b/tests/unit/converter/test_template_segment_converter.py index f99b252c29..1add19c45e 100644 --- a/tests/unit/converter/test_template_segment_converter.py +++ b/tests/unit/converter/test_template_segment_converter.py @@ -171,7 +171,7 @@ async def test_template_segment_converter_invalid_input_type(): await converter.convert_async(prompt="test", input_type="image_path") -def test_template_segment_converter_identifier_includes_ordered_parameters(): +def test_template_segment_converter_identifier_includes_ordered_parameters() -> None: """Test that the converter identifier includes ordered parameter names.""" template = SeedPrompt(value="{{ left }} and {{ right }}", parameters=["left", "right"]) converter = TemplateSegmentConverter(prompt_template=template, seed=42) @@ -183,7 +183,7 @@ def test_template_segment_converter_identifier_includes_ordered_parameters(): assert "template_hash" in identifier.params -def test_template_segment_converter_identifier_differs_when_parameters_reordered(): +def test_template_segment_converter_identifier_differs_when_parameters_reordered() -> None: """Reordering the template parameters changes binding behavior and must yield distinct hashes.""" template = "LEFT[{{left}}] RIGHT[{{right}}]" first = TemplateSegmentConverter( @@ -200,7 +200,7 @@ def test_template_segment_converter_identifier_differs_when_parameters_reordered assert second.get_identifier().params["parameters"] == ["right", "left"] -async def test_template_segment_converter_reordered_parameters_coexist_in_registry(): +async def test_template_segment_converter_reordered_parameters_coexist_in_registry_async() -> None: """Distinct parameter bindings have distinct default registry names and register without conflict.""" registry = ConverterRegistry() template = "LEFT[{{left}}] RIGHT[{{right}}]"