Repository navigation
Description
Describe the bug
Attack rows written before the atomic attack identifier grew its nested structure keep the attack class name and converter lists at $.children.attack instead of $.children.attack_technique.children.attack. The SDK displays these rows correctly (get_attack_strategy_identifier falls back to the older layout) and the analytics queries resolve the same fallback, but the attack history filters and the options endpoints only read the nested layout, so for these rows:
- filtering
GET /api/attacksby the attack type shown in the list returns nothing, GET /api/attacks/attack-optionsomits the type, so the dropdown cannot even produce the value,has_converters=truedrops rows whose older layout records converters, andconverter_typescannot find them.
Steps/Code to Reproduce
import asyncio, uuid
from datetime import UTC, datetime
from pyrit.memory.memory_models import AttackResultEntry
from pyrit.memory.sqlite_memory import SQLiteMemory
async def main():
memory = SQLiteMemory(db_path=":memory:", silent=True)
await memory.initialize_async()
legacy = {
"class_name": "AtomicAttack",
"children": {"attack": {
"class_name": "LegacyAttack",
"children": {"request_converters": [{"class_name": "LegacyRequest"}]},
}},
}
async with memory._get_async_engine().begin() as connection:
await connection.execute(AttackResultEntry.__table__.insert().values(
id=uuid.UUID(int=1), conversation_id=str(uuid.UUID(int=2)),
objective="legacy objective", outcome="undetermined",
timestamp=datetime(2026, 1, 1, tzinfo=UTC),
atomic_attack_identifier=legacy, atomic_attack_identifier_hash=None))
rows = await memory.get_attack_results_async()
print("displayed type:", rows[0].get_attack_strategy_identifier().class_name) # LegacyAttack
print("filter by it:", len(await memory.get_attack_results_async(attack_classes=["LegacyAttack"])))
print("options:", await memory.get_unique_attack_class_names_async())
print("has converters:", len(await memory.get_attack_results_async(has_converters=True)))
asyncio.run(main())Expected Results
The displayed type filters its own row, appears in the options list, and the row's recorded converter keeps it in has_converters=true, matching what analytics already does for the same stored shape.
Actual Results
The filter returns 0 rows, the options list is empty, and has_converters=true returns 0 rows even though the row records a request converter.
Screenshots
Not applicable.
Versions
OS: macOS
Python version: 3.13
PyRIT version: main at 165bdbd
Activity