FEAT Backend attack API: conversation-centric redesign with multi-conversation workspaces and media serving#1419
Merged
romanlutz merged 59 commits intomicrosoft:mainfrom Mar 9, 2026
Conversation
- Add run_initializers_async to pyrit.setup for programmatic initialization - Switch AIRTInitializer to Entra (Azure AD) auth, removing API key requirements - Add --config-file flag to pyrit_backend CLI - Use PyRIT configuration loader in FrontendCore and pyrit_backend - Update AIRTTargetInitializer with new target types Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add conversation_stats model and attack_result extensions - Add get_attack_results with filtering by harm categories, labels, attack type, and converter types to memory interface - Implement SQLite-specific JSON filtering for attack results - Add memory_models field for targeted_harm_categories - Add prompt_metadata support to openai image/video/response targets - Fix missing return statements in SQLite harm_category and label filters Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add attack CRUD routes with conversation management - Add message sending with target dispatch and response handling - Add attack mappers for domain-to-DTO conversion with signed blob URLs - Add attack service with video remix support and piece persistence - Expand target service and routes with registry-based target management - Add version endpoint with database info Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Builds out the PyRIT backend from a scaffold into an attack-centric API, adding multi-conversation attack support, conversation summarization, target registry interactions, and memory-layer enhancements (including Entra-auth-related initializer updates).
Changes:
- Adds attack/conversation backend endpoints keyed by
attack_result_id, plus message sending that dispatches to targets by registry name. - Introduces
ConversationStatsand new memory APIs to efficiently summarize conversations without loading full message pieces. - Updates setup/CLI and initializers (incl.
run_initializers_async+ config-file support) and expands OpenAI target/mapping behavior for richer metadata/media handling.
Reviewed changes
Copilot reviewed 43 out of 43 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/target/test_video_target.py | Adds regression test for single-turn enforcement on video target requests. |
| tests/unit/target/test_image_target.py | Adds async regression test for single-turn enforcement on image target requests. |
| tests/unit/setup/test_airt_targets_initializer.py | Adds test coverage for GPT-5 high-reasoning Responses target extra body parameters. |
| tests/unit/setup/test_airt_initializer.py | Updates AIRT initializer tests to reflect Entra/token auth (no API key env vars). |
| tests/unit/memory/test_sqlite_memory.py | Adds unit tests for get_conversation_stats aggregation behavior. |
| tests/unit/memory/memory_interface/test_interface_attack_results.py | Expands tests for attack result dedupe + update semantics and renames attack/converter filters. |
| tests/unit/cli/test_pyrit_backend.py | Adds CLI tests for pyrit_backend arg parsing and initialization wiring. |
| tests/unit/cli/test_frontend_core.py | Updates FrontendCore expectations + patches to new initialization flow and defaults. |
| tests/unit/backend/test_target_service.py | Updates tests for renamed target fields (target_registry_name). |
| tests/unit/backend/test_mappers.py | Adds extensive mapper tests for media handling, blob URL signing/fetching, and summary mapping changes. |
| tests/unit/backend/test_main.py | Updates lifespan tests to reflect CLI-driven initialization instead of in-app initialization. |
| tests/unit/backend/test_api_routes.py | Updates route tests for new endpoints, request/response shapes, and renamed filters/fields. |
| pyrit/setup/initializers/airt_targets.py | Adds target config extra_kwargs and new GPT-5 high-reasoning Responses target; adjusts video target config naming/env vars. |
| pyrit/setup/initializers/airt.py | Switches AIRTInitializer to Entra/token auth and updates scorer setup accordingly. |
| pyrit/setup/initialization.py | Adds run_initializers_async helper to run initializers without reinitializing memory. |
| pyrit/setup/init.py | Re-exports run_initializers_async. |
| pyrit/prompt_target/openai/openai_video_target.py | Adds validation to enforce single-turn conversations for video target usage. |
| pyrit/prompt_target/openai/openai_target.py | Refactors OpenAITarget base inheritance/initialization behavior. |
| pyrit/prompt_target/openai/openai_response_target.py | Includes extra_body_parameters in target identifiers for richer provenance. |
| pyrit/prompt_target/openai/openai_realtime_target.py | Adjusts realtime target inheritance to ensure chat-target semantics. |
| pyrit/prompt_target/openai/openai_image_target.py | Adds validation to enforce single-turn conversations for image target usage. |
| pyrit/models/conversation_stats.py | Introduces ConversationStats dataclass for lightweight conversation summaries. |
| pyrit/models/attack_result.py | Adds attack_result_id to expose persisted identifier to callers. |
| pyrit/models/init.py | Exports ConversationStats. |
| pyrit/memory/sqlite_memory.py | Implements get_conversation_stats, improves update semantics, and renames attack/converter filter helpers. |
| pyrit/memory/memory_models.py | Populates attack_result_id when constructing AttackResult from DB entry. |
| pyrit/memory/memory_interface.py | Adds get_conversation_stats, exposes duplicate_messages, and updates attack/converter filter APIs + update helpers. |
| pyrit/memory/azure_sql_memory.py | Mirrors SQLite changes for attack/converter filters and implements get_conversation_stats. |
| pyrit/cli/pyrit_backend.py | Refactors backend CLI to use FrontendCore + adds --config-file. |
| pyrit/cli/frontend_core.py | Splits “initialize” vs “run initializers” behavior and reuses run_initializers_async. |
| pyrit/backend/services/target_service.py | Renames API surface to target_registry_name and adjusts pagination cursor semantics. |
| pyrit/backend/services/attack_service.py | Major refactor: attack_result_id-keyed operations, multi-conversation support, branching, message routing, and async DTO mapping. |
| pyrit/backend/routes/version.py | Adds optional database backend info to version response. |
| pyrit/backend/routes/targets.py | Renames target route params to target_registry_name. |
| pyrit/backend/routes/attacks.py | Adds conversation management endpoints and refactors message endpoints to support per-conversation routing. |
| pyrit/backend/models/targets.py | Updates DTO fields (registry naming) and adds supports_multiturn_chat. |
| pyrit/backend/models/attacks.py | Refactors attack DTOs for attack_result_id, target info nesting, conversation endpoints, and message routing fields. |
| pyrit/backend/models/init.py | Updates exported backend models to reflect renamed/removed DTOs. |
| pyrit/backend/mappers/target_mappers.py | Updates target mapping to registry naming and adds multiturn capability detection. |
| pyrit/backend/mappers/attack_mappers.py | Refactors summary mapping to use ConversationStats and adds media/blob URL signing/fetching logic + async message DTO mapping. |
| pyrit/backend/mappers/init.py | Exports pyrit_messages_to_dto_async instead of sync variant. |
| pyrit/backend/main.py | Removes implicit initialization in lifespan; warns when started without CLI initialization. |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
tests/unit/memory/memory_interface/test_interface_attack_results.py
Outdated
Show resolved
Hide resolved
tests/unit/memory/memory_interface/test_interface_attack_results.py
Outdated
Show resolved
Hide resolved
tests/unit/memory/memory_interface/test_interface_attack_results.py
Outdated
Show resolved
Hide resolved
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
romanlutz
added a commit
to romanlutz/PyRIT
that referenced
this pull request
Mar 5, 2026
Resolve merge conflicts favoring backend-attack-api (PR microsoft#1419) changes: - Use set-based deduplication for conversation IDs - Use FrontendCore in pyrit_backend CLI - Remove old video_id injection helpers (moved to video target) - Use ConversationStats-based aggregation - Fix SAS cache TTL, score DTOs, version endpoint - Add Path import to pyrit_backend.py - Fix created_at type (datetime, not str) in list_conversations Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add 400 bad request handling to create_related_conversation endpoint with validation for source_conversation_id belonging to the attack - Add HTTPException 500 to serve_media_async docstring - Increase filename hash truncation from 8 to 12 characters - Rename initialize_and_run to initialize_and_run_async per convention - Handle /api/media?path= URLs and existing file paths in _persist_base64_pieces_async to prevent base64 decode errors - Replace attack_result_id or '' fallbacks with RuntimeError assertions - Merge latest main Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 29 out of 29 changed files in this pull request and generated 1 comment.
You can also share your feedback on Copilot code review. Take the survey.
…itive extensions Prevents exfiltration of database files and other sensitive data by: - Only serving files from prompt-memory-entries/ and seed-prompt-entries/ - Blocking .db, .sqlite, .json, .yaml, .env and other sensitive extensions - Rejecting files in the results_path root directory Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ValbuenaVC
reviewed
Mar 7, 2026
- Fix phase numbering in list_attacks_async (Phase 2 -> Phase 3) - Add TODO comment on RuntimeError for attack_result_id assertion - Split add_message_async into focused helpers: _validate_target_match, _validate_operator_match, _resolve_labels, _update_attack_after_message_async - Rename op_name label to operator_name for clarity - Add comment explaining results[0] usage with unique ID queries - Remove duplicate source_conversation_id validation - Rename ChangeMainConversation -> UpdateMainConversation (request, response, route, service method) - Add updated_at field to UpdateMainConversationResponse Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Instead of leaving attack_result_id as None and relying on DB backfill, generate a UUID in the constructor when not provided. This ensures attack_result_id is always populated, eliminating the need for None checks and RuntimeError assertions. - AttackResult.attack_result_id: Optional[str]=None -> str=uuid4() - AttackResultEntry uses entry.attack_result_id instead of new uuid - Remove backfill loop from add_attack_results_to_memory - Remove RuntimeError checks in attack_service.py and attack_mappers.py Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The frontend uses 'operator' and 'operation' as label keys. The previous rename from op_name to operator_name was incorrect - op_name referred to 'operation', not 'operator'. Fix the operator validation to use the correct 'operator' label key. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rename all label key occurrences across codebase: - op_name -> operation - user_name/username -> operator Updated in backend service, tests, and documentation files (.py and .ipynb) for consistency with frontend LabelsBar defaults. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Switch media endpoint from extension blocklist to allowlist - Use consistent db_type format 'Type (None)' when db_name is absent Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace uuid-based test_operation/test_operator with readable examples 'op_trash_panda' and 'roakey' matching frontend defaults. Remove unused uuid imports. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…bject Add methods to AttackResult: - includes_conversation(): check if a conversation belongs to this attack - get_all_conversation_ids(): main + all related - get_active_conversation_ids(): main + pruned (user-visible) - get_pruned_conversation_ids(): pruned only Refactor AttackService to delegate to these methods instead of inline set comprehensions, reducing duplication across 5 methods. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
hannahwestra25
approved these changes
Mar 9, 2026
riyosha
pushed a commit
to riyosha/PyRIT
that referenced
this pull request
Mar 24, 2026
…versation workspaces and media serving (microsoft#1419) Co-authored-by: Roman Lutz <romanlutz@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Backend Attack API: conversation-centric redesign
Why
The original backend API modeled attacks as single-conversation entities: one attack, one conversation, one target. This doesn't match how red-teaming actually works. Operators need to branch
conversations mid-attack to explore alternative paths, compare outcomes, or retry with different system prompts — all without losing the original conversation history.
This PR restructures the API around the idea that an attack is a workspace containing multiple conversations. A new attack still starts with one "main" conversation, but operators can now branch from
any point in that conversation, send messages to specific branches, and swap which branch is considered "main."
What changed
Conversation management (
/attacks/{id}/conversations)source_conversation_id+cutoff_index), and swap main conversations within an attacktarget_conversation_idsource_conversation_idis validated against the attack's known conversations to prevent cross-attack history leakageADVERSARIAL→PRUNEDon swap)Media serving (
/api/media)image_path,audio_path,video_path) viaFileResponsewith proper MIME typesresults_pathare servedAttack list & discovery
/attacksreturns paginated summaries with aggregatedConversationStats(turn counts, labels, preview) instead of loading full message pieces/attack-optionsand/converter-optionspopulate UI dropdowns from database historyScore model enhancements
ScoreDTO includesscore_typeandscore_categorytrue_falsescores exposescore_value_as_strfor readable displayVideo target improvements
_validate_video_remix_piecesenforcesvideo_idpresence and consistency across text + video_path piecesAttackServiceintoOpenAIVideoTargetwhere it belongsprompt_metadatafield added toMessagePieceRequestDTO for passingvideo_idthrough the APIBackend models & exports
backend/models/__init__.pyconsolidates all DTO exports; no missing re-exportsPrependedMessageRequest,TargetInfo,AttackOptionsResponse,ConverterOptionsResponse, and other models properly exportedCLI
pyrit_backendCLI gains--config-fileargument and usesFrontendCorefor initialization (consistent withpyrit_scan/pyrit_shell)frontend_core.pyimports moved to module level per style guideVersion endpoint (
/api/version)What didn't change
The memory layer (
MemoryInterface,SQLiteMemory, models) is unchanged — those pieces landed in earlier PRs. This PR builds the API surface on top of the existing memory primitives.Test coverage
pyrit/backend/(1137 statements, 13 uncovered — all Azure credential or DB-detection paths)test_media_route.py(media endpoint),test_pyrit_backend.py(CLI), expandedtest_video_target.py(remix validation)