From 8dea3a96021f3860c2c1da103ebf2acfacee0536 Mon Sep 17 00:00:00 2001 From: Bolor Date: Tue, 17 Mar 2026 15:25:16 -0700 Subject: [PATCH 1/4] Fix JSON path for converter class names in attack result queries Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- pyrit/memory/azure_sql_memory.py | 10 ++++------ pyrit/memory/sqlite_memory.py | 6 +++--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/pyrit/memory/azure_sql_memory.py b/pyrit/memory/azure_sql_memory.py index 26eed54d0..6160c1907 100644 --- a/pyrit/memory/azure_sql_memory.py +++ b/pyrit/memory/azure_sql_memory.py @@ -423,14 +423,12 @@ def _get_attack_result_converter_classes_condition(self, *, converter_classes: S Any: SQLAlchemy combined condition with bound parameters. """ if len(converter_classes) == 0: - # Explicitly "no converters": match attacks where the converter list - # is absent, null, or empty in the stored JSON. return text( """("AttackResultEntries".atomic_attack_identifier IS NULL OR JSON_QUERY("AttackResultEntries".atomic_attack_identifier, - '$.children.attack.request_converter_identifiers') IS NULL + '$.children.attack.children.request_converters') IS NULL OR JSON_QUERY("AttackResultEntries".atomic_attack_identifier, - '$.children.attack.request_converter_identifiers') = '[]')""" + '$.children.attack.children.request_converters') = '[]')""" ) conditions = [] @@ -439,7 +437,7 @@ def _get_attack_result_converter_classes_condition(self, *, converter_classes: S param_name = f"conv_cls_{i}" conditions.append( f"""EXISTS(SELECT 1 FROM OPENJSON(JSON_QUERY("AttackResultEntries".atomic_attack_identifier, - '$.children.attack.request_converter_identifiers')) + '$.children.attack.children.request_converters')) WHERE LOWER(JSON_VALUE(value, '$.class_name')) = :{param_name})""" ) bindparams_dict[param_name] = cls.lower() @@ -485,7 +483,7 @@ def get_unique_converter_class_names(self) -> list[str]: """SELECT DISTINCT JSON_VALUE(c.value, '$.class_name') AS cls FROM "AttackResultEntries" CROSS APPLY OPENJSON(JSON_QUERY(atomic_attack_identifier, - '$.children.attack.request_converter_identifiers')) AS c + '$.children.attack.children.request_converters')) AS c WHERE ISJSON(atomic_attack_identifier) = 1 AND JSON_VALUE(c.value, '$.class_name') IS NOT NULL""" ) diff --git a/pyrit/memory/sqlite_memory.py b/pyrit/memory/sqlite_memory.py index 58ae9098e..ba5607943 100644 --- a/pyrit/memory/sqlite_memory.py +++ b/pyrit/memory/sqlite_memory.py @@ -540,7 +540,7 @@ def _get_attack_result_converter_classes_condition(self, *, converter_classes: S # is absent, null, or empty in the stored JSON. converter_json = func.json_extract( AttackResultEntry.atomic_attack_identifier, - "$.children.attack.request_converter_identifiers", + "$.children.attack.children.request_converters", ) return or_( AttackResultEntry.atomic_attack_identifier.is_(None), @@ -555,7 +555,7 @@ def _get_attack_result_converter_classes_condition(self, *, converter_classes: S text( f"""EXISTS(SELECT 1 FROM json_each( json_extract("AttackResultEntries".atomic_attack_identifier, - '$.children.attack.request_converter_identifiers')) + '$.children.attack.children.request_converters')) WHERE LOWER(json_extract(value, '$.class_name')) = :{param_name})""" ).bindparams(**{param_name: cls.lower()}) ) @@ -592,7 +592,7 @@ def get_unique_converter_class_names(self) -> list[str]: FROM "AttackResultEntries", json_each( json_extract("AttackResultEntries".atomic_attack_identifier, - '$.children.attack.request_converter_identifiers') + '$.children.attack.children.request_converters') ) AS j WHERE cls IS NOT NULL""" ) From de7655ec126494b486d191ce2936fa87280a6806 Mon Sep 17 00:00:00 2001 From: Bolor Date: Tue, 17 Mar 2026 15:39:48 -0700 Subject: [PATCH 2/4] fix unit test --- .../memory_interface/test_interface_attack_results.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/unit/memory/memory_interface/test_interface_attack_results.py b/tests/unit/memory/memory_interface/test_interface_attack_results.py index 3c8d13267..dee46b081 100644 --- a/tests/unit/memory/memory_interface/test_interface_attack_results.py +++ b/tests/unit/memory/memory_interface/test_interface_attack_results.py @@ -1131,13 +1131,13 @@ def _make_attack_result_with_identifier( converter_class_names: Optional[list[str]] = None, ) -> AttackResult: """Helper to create an AttackResult with a ComponentIdentifier containing converters.""" - params = {} + children: dict = {} if converter_class_names is not None: - params["request_converter_identifiers"] = [ + children["request_converters"] = [ ComponentIdentifier( class_name=name, class_module="pyrit.converters", - ).to_dict() + ) for name in converter_class_names ] @@ -1148,7 +1148,7 @@ def _make_attack_result_with_identifier( attack_identifier=ComponentIdentifier( class_name=class_name, class_module="pyrit.attacks", - params=params, + children=children, ), ), ) From 71610ac886b0d90590b8e81d5bf0d2dcd20c32e3 Mon Sep 17 00:00:00 2001 From: Bolor Date: Tue, 17 Mar 2026 15:43:47 -0700 Subject: [PATCH 3/4] added comment back in --- pyrit/memory/azure_sql_memory.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyrit/memory/azure_sql_memory.py b/pyrit/memory/azure_sql_memory.py index 6160c1907..d97b9f6d8 100644 --- a/pyrit/memory/azure_sql_memory.py +++ b/pyrit/memory/azure_sql_memory.py @@ -423,6 +423,8 @@ def _get_attack_result_converter_classes_condition(self, *, converter_classes: S Any: SQLAlchemy combined condition with bound parameters. """ if len(converter_classes) == 0: + # Explicitly "no converters": match attacks where the converter list + # is absent, null, or empty in the stored JSON. return text( """("AttackResultEntries".atomic_attack_identifier IS NULL OR JSON_QUERY("AttackResultEntries".atomic_attack_identifier, From 493a8ce9731822c2df99290f31866b4924ddb937 Mon Sep 17 00:00:00 2001 From: Bolor Date: Tue, 17 Mar 2026 15:51:11 -0700 Subject: [PATCH 4/4] addressing copilot comments --- pyrit/memory/azure_sql_memory.py | 4 ++-- pyrit/memory/sqlite_memory.py | 6 +++--- .../memory_interface/test_interface_attack_results.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pyrit/memory/azure_sql_memory.py b/pyrit/memory/azure_sql_memory.py index d97b9f6d8..c9f349c0d 100644 --- a/pyrit/memory/azure_sql_memory.py +++ b/pyrit/memory/azure_sql_memory.py @@ -473,8 +473,8 @@ def get_unique_attack_class_names(self) -> list[str]: def get_unique_converter_class_names(self) -> list[str]: """ Azure SQL implementation: extract unique converter class_name values - from the request_converter_identifiers array in the atomic_attack_identifier - JSON column. + from the children.attack.children.request_converters array + in the atomic_attack_identifier JSON column. Returns: Sorted list of unique converter class name strings. diff --git a/pyrit/memory/sqlite_memory.py b/pyrit/memory/sqlite_memory.py index ba5607943..d59a6571d 100644 --- a/pyrit/memory/sqlite_memory.py +++ b/pyrit/memory/sqlite_memory.py @@ -528,7 +528,7 @@ def _get_attack_result_converter_classes_condition(self, *, converter_classes: S Uses json_extract() on the atomic_attack_identifier JSON column. When converter_classes is empty, matches attacks with no converters - (request_converter_identifiers is absent or null in the JSON). + (children.attack.children.request_converters is absent or null in the JSON). When non-empty, uses json_each() to check all specified classes are present (AND logic, case-insensitive). @@ -579,8 +579,8 @@ def get_unique_attack_class_names(self) -> list[str]: def get_unique_converter_class_names(self) -> list[str]: """ SQLite implementation: extract unique converter class_name values - from the request_converter_identifiers array in the atomic_attack_identifier - JSON column. + from the children.attack.children.request_converters array in the + atomic_attack_identifier JSON column. Returns: Sorted list of unique converter class name strings. diff --git a/tests/unit/memory/memory_interface/test_interface_attack_results.py b/tests/unit/memory/memory_interface/test_interface_attack_results.py index dee46b081..91367c3a1 100644 --- a/tests/unit/memory/memory_interface/test_interface_attack_results.py +++ b/tests/unit/memory/memory_interface/test_interface_attack_results.py @@ -1136,7 +1136,7 @@ def _make_attack_result_with_identifier( children["request_converters"] = [ ComponentIdentifier( class_name=name, - class_module="pyrit.converters", + class_module="pyrit.prompt_converter", ) for name in converter_class_names ]