Skip to content

FEAT Wire frontend attack view to backend APIs#1371

Open
romanlutz wants to merge 118 commits intoAzure:mainfrom
romanlutz:romanlutz/frontend_attack_view
Open

FEAT Wire frontend attack view to backend APIs#1371
romanlutz wants to merge 118 commits intoAzure:mainfrom
romanlutz:romanlutz/frontend_attack_view

Conversation

@romanlutz
Copy link
Contributor

@romanlutz romanlutz commented Feb 17, 2026

Add target management and real attack execution to the PyRIT frontend, replacing the echo stub with live backend communication.

Backend:

  • Add attack CRUD endpoints (create, list, get, messages, add message)
  • Replace target_unique_name with target_registry_name across models, routes, services, and mappers for clarity
  • Add TargetInfo model to nest target metadata in AttackSummary
  • Add target_registry_name to AddMessageRequest so the backend stays stateless (no reverse lookups from attack identifier)
  • Move lifespan init to pyrit_backend CLI; main.py warns if run directly

CLI:

  • Add FrontendCore.run_initializers_async() to consolidate initializer resolution used by both pyrit_backend and run_scenario_async
  • Move all deferred imports to module level in frontend_core.py
  • Refactor pyrit_backend to use FrontendCore two-step init pattern (initialize_async + run_initializers_async)

Frontend:

  • Add Config page with target list, create target dialog, and set-active-target flow
  • Wire ChatWindow to attacksApi: lazy attack creation on first message, send via PromptNormalizer, map backend responses to UI messages
  • Add messageMapper utils (backend DTO <-> frontend Message conversion)
  • Add full backend DTO types mirroring pyrit/backend/models
  • Support simulated_assistant role, error rendering, loading indicators, and media attachments (image/audio/video)

Tests:

  • Add/update 300+ backend unit tests covering attack service, mappers, target service, API routes, and main lifespan
  • Add 150+ frontend tests covering ChatWindow, TargetConfig, CreateTargetDialog, MessageList, api service, and messageMapper
  • Update test_frontend_core patch targets to match top-level imports

Screenshots:

Landing page
image

History view
image

Target config view
Untitled

Text-to-text
image
Branching into new conversation after first turn
image

Text-to-image
image
Reusing the same image for text+image-to-image
image
image

Text-to-video
image
Text+video-to-video
image

@hannahwestra25
Copy link
Contributor

Can you add screenshots for the frontend

romanlutz added a commit to romanlutz/PyRIT that referenced this pull request Feb 23, 2026
Backend:
- Replace private CentralMemory._memory_instance access with try/except
  around the public get_memory_instance() API in the lifespan handler.

Initialization:
- Extract run_initializers_async() as a public function in
  pyrit.setup.initialization so initializer execution can be invoked
  without redundantly re-loading env files, resetting defaults, and
  re-creating the memory instance.
- FrontendCore.run_initializers_async() now calls the new function
  directly instead of re-invoking initialize_pyrit_async.
- Export run_initializers_async from pyrit.setup.

Frontend:
- Extract TargetTable into its own component (TargetTable.tsx).
- Move makeStyles definitions to co-located .styles.ts files for
  TargetConfig, TargetTable, and CreateTargetDialog.
- Remove redundant explicit generic in useState<string>('') calls.
- Use FluentUI Field validationMessage/validationState props for
  inline field-level validation in CreateTargetDialog.

Tests:
- Update TestRunScenarioAsync patches to mock run_initializers_async
  instead of initialize_pyrit_async.
@romanlutz romanlutz force-pushed the romanlutz/frontend_attack_view branch from c799aa4 to 7e30544 Compare February 23, 2026 05:12
Copilot AI review requested due to automatic review settings February 28, 2026 14:22
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Wires the PyRIT frontend attack experience to live backend APIs, including target management, attack execution, conversation branching, and richer message rendering.

Changes:

  • Added/updated backend attack + target endpoints and DTOs (including conversation summaries and target metadata).
  • Refactored initialization flow to shift lifespan init into CLI and consolidate initializer execution.
  • Expanded frontend UI (config/history/chat) and added extensive unit + E2E coverage.

Reviewed changes

Copilot reviewed 84 out of 88 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
tests/unit/registry/test_converter_registry.py Adds unit tests for converter registry singleton/metadata behavior.
tests/unit/memory/test_sqlite_memory.py Adds tests for new conversation stats aggregation in SQLite memory.
tests/unit/cli/test_pyrit_backend.py Adds tests for CLI config-file forwarding and server startup flow.
tests/unit/cli/test_frontend_core.py Updates patch targets and initializer execution expectations after refactor.
tests/unit/backend/test_target_service.py Updates tests for target registry naming changes.
tests/unit/backend/test_main.py Updates lifespan expectations to “warn only” behavior.
pyrit/setup/initializers/airt_targets.py Adds extra kwargs support and new/renamed target presets.
pyrit/setup/initializers/airt.py Switches auth approach to Entra token providers and updates required vars.
pyrit/setup/initialization.py Extracts run_initializers_async to separate initializer execution from memory init.
pyrit/setup/init.py Exposes run_initializers_async as part of setup public API.
pyrit/prompt_target/openai/openai_video_target.py Enforces single-turn conversation constraint for video target.
pyrit/prompt_target/openai/openai_target.py Refactors OpenAI base target inheritance/init to align with PromptTarget.
pyrit/prompt_target/openai/openai_response_target.py Includes target-specific params in identifiers (e.g., extra body params).
pyrit/prompt_target/openai/openai_realtime_target.py Adjusts inheritance to keep chat-target semantics for realtime target.
pyrit/prompt_target/openai/openai_image_target.py Enforces single-turn conversation constraint for image target.
pyrit/models/conversation_stats.py Introduces ConversationStats aggregate model.
pyrit/models/attack_result.py Adds attack_result_id onto domain AttackResult.
pyrit/models/init.py Exports ConversationStats from models package.
pyrit/memory/sqlite_memory.py Adds conversation stats query and refactors some filtering helpers/update behavior.
pyrit/memory/memory_models.py Maps DB primary key into AttackResult.attack_result_id.
pyrit/memory/memory_interface.py Adds conversation stats API and updates attack result insert/update semantics.
pyrit/memory/azure_sql_memory.py Adds Azure SQL implementation for conversation stats and safer update behavior.
pyrit/cli/pyrit_backend.py Refactors CLI to use FrontendCore two-step init and adds --config-file.
pyrit/cli/frontend_core.py Moves deferred imports to module-level and adds run_initializers_async method.
pyrit/backend/services/target_service.py Renames target id field to registry name and updates pagination cursor.
pyrit/backend/routes/version.py Adds database backend info to version response payload.
pyrit/backend/routes/targets.py Renames path params and docs to registry naming scheme.
pyrit/backend/routes/attacks.py Expands attack routes to support conversations and changes identifiers to attack_result_id.
pyrit/backend/models/targets.py DTO rename + adds supports_multiturn_chat.
pyrit/backend/models/attacks.py Adds target metadata nesting, conversation endpoints, and new message request fields.
pyrit/backend/models/init.py Updates exports for renamed message response DTO.
pyrit/backend/mappers/target_mappers.py Maps multiturn capability and renames target id field in DTO mapping.
pyrit/backend/mappers/init.py Renames exported async mapper function.
pyrit/backend/main.py Removes standalone uvicorn runner and changes lifespan to warn when uninitialized.
frontend/src/utils/messageMapper.ts Adds backend DTO ↔ UI Message mapping (attachments, reasoning, errors).
frontend/src/types/index.ts Adds backend DTO type mirrors and expands UI message model.
frontend/src/services/api.ts Adds targets/attacks/labels API clients and query serialization.
frontend/src/services/api.test.ts Expands mocked API service tests for new endpoints.
frontend/src/components/Sidebar/Navigation.tsx Adds navigation views (chat/history/config) and active styling.
frontend/src/components/Sidebar/Navigation.test.tsx Updates navigation tests for new view switching behavior.
frontend/src/components/Layout/MainLayout.tsx Shows DB info in version tooltip and wires navigation callbacks.
frontend/src/components/Layout/MainLayout.test.tsx Updates layout tests for new navigation props and DB tooltip behavior.
frontend/src/components/Labels/LabelsBar.test.tsx Adds unit tests for labels UI behavior and label fetching.
frontend/src/components/Config/TargetTable.tsx Adds target list table UI with active-target selection controls.
frontend/src/components/Config/TargetTable.styles.ts Adds styling for target table and active row highlighting.
frontend/src/components/Config/TargetConfig.tsx Implements target config page with fetch/retry, refresh, and create dialog.
frontend/src/components/Config/TargetConfig.test.tsx Adds tests for config page states and interactions.
frontend/src/components/Config/TargetConfig.styles.ts Adds styling for config page layout and states.
frontend/src/components/Config/CreateTargetDialog.tsx Adds create-target dialog and validation + submit flow.
frontend/src/components/Config/CreateTargetDialog.test.tsx Adds tests for create-target dialog validation and submission.
frontend/src/components/Config/CreateTargetDialog.styles.ts Adds styling for create-target dialog layout.
frontend/src/components/Chat/InputBox.tsx Adds banners/locking states, ref API, and multiturn warnings for active target.
frontend/src/components/Chat/InputBox.test.tsx Adds tests for new input-box behaviors (single-turn, ref attachments, banners).
frontend/src/components/Chat/ConversationPanel.tsx Adds conversation list panel for attacks with promote-to-main and new conversation actions.
frontend/src/components/Chat/ConversationPanel.test.tsx Adds tests for conversation panel rendering and interactions.
frontend/src/App.tsx Introduces multi-view app shell, target selection, attack loading, and global labels.
frontend/src/App.test.tsx Expands app tests for navigation, target selection, and opening historical attacks.
frontend/playwright.config.ts Splits Playwright projects into seeded vs live modes.
frontend/package.json Adds e2e scripts for seeded and live test projects.
frontend/eslint.config.js Adds Node globals for Playwright e2e files.
frontend/e2e/config.spec.ts Adds e2e coverage for config page and config↔chat flow.
frontend/e2e/api.spec.ts Adds e2e API smoke tests (targets/attacks) and improves slow-backend handling.
frontend/e2e/accessibility.spec.ts Updates a11y coverage for new navigation and config table; adjusts expected header text.
frontend/dev.py Improves dev runner process management, adds detach/logs/config-file support.
frontend/README.md Documents seeded vs live e2e modes.
.github/workflows/frontend_tests.yml Runs seeded-only e2e in GitHub Actions.
.gitattributes Adds union merge strategy for squad log/state files.
.devcontainer/devcontainer_setup.sh Makes Playwright install failures non-blocking with clearer messaging.

Copilot AI review requested due to automatic review settings February 28, 2026 14:34
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 84 out of 88 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (1)

pyrit/memory/sqlite_memory.py:1

  • This function used to return the exists().where(...) condition, but now assigns it to labels_subquery and never returns it. That will cause callers to append None to SQLAlchemy conditions and break attack filtering by labels. Return labels_subquery (or inline it back into a return statement).
# Copyright (c) Microsoft Corporation.

romanlutz and others added 5 commits February 28, 2026 14:49
- 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>
- Add attack-centric chat UI with multi-conversation support
- Add conversation panel with branching and message actions
- Add attack history view with filtering
- Add labels bar for attack metadata
- Add target configuration with create dialog
- Add message mapper utilities for backend/frontend translation
- Add video playback support with signed blob URLs
- Add InputBox with attachment support and auto-expand
- Update dev.py with --detach, logs, and process management
- Add e2e tests for chat, config, and flow scenarios

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 28, 2026 14:55
@romanlutz romanlutz force-pushed the romanlutz/frontend_attack_view branch from 8f1a532 to 65a4182 Compare February 28, 2026 14:55
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 83 out of 88 changed files in this pull request and generated 4 comments.

romanlutz and others added 12 commits March 10, 2026 06:56
Add tests at all three levels to prevent regression:
- Mapper: extra params collected, None-valued excluded, merge with explicit
- API routes: list and get endpoints include target_specific_params in JSON
- Service: list and get return target_specific_params from registry objects
- Frontend: TargetConfig renders reasoning_effort, reasoning_summary etc.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When branching into a new conversation or copying to a new conversation
within the same attack, the conversations panel now opens automatically
so users can see and navigate between conversations.

Added setIsPanelOpen(true) to handleCopyToNewConversation and
handleBranchConversation handlers, plus 2 new tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When a new attack is created, capture the active target as attackTarget
so the cross-target guard fires if the user switches targets mid-
conversation. Previously attackTarget was only set when loading
historical attacks, so switching targets during a new attack was
not blocked.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When a previously generated video is carried over to a new conversation
(via copy/branch) and sent with a text prompt, the backend now
auto-resolves the video_id from the original piece's metadata and sets
it on both the text and video_path pieces. This enables seamless video
remixing without requiring the frontend to manage video_id metadata.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The frontend now carries prompt_metadata (including video_id) through
the full pipeline: backend response → MessageAttachment → outgoing
request. This ensures video_id is properly set on both text and video
pieces when remixing, without relying solely on the backend fallback.

Changes:
- Add metadata field to MessageAttachment type
- Add prompt_metadata to BackendMessagePiece and MessagePiece DTOs
- Preserve prompt_metadata in pieceToAttachment mapper
- Include prompt_metadata in attachmentToMessagePieceRequest
- Auto-set video_id on text piece in buildMessagePieces when video
  attachment with metadata is present
- Include prompt_metadata in backend pyrit_messages_to_dto_async mapper

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The video target's _validate_video_remix_pieces was stripping
video_path pieces from the message in-place before the normalizer
stored the request. This meant the user's sent message only showed
the text piece, not the video attachment. The stripping was unnecessary
since the target only uses video_id from prompt_metadata for remix.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
isMediaDataType() now includes 'binary' so that binary_path pieces
(PDFs, Word docs, etc.) are rendered as file attachments rather than
being dumped into the text content area.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The backend expects the query parameter 'label' (repeatable key:value)
but the frontend type definition used 'labels'. The caller in
AttackHistory.tsx was already using 'label' correctly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…r attacks

FastAPI parses ?converter_types= as [''], not []. The service treats
[] as 'only attacks with no converters' but [''] would try to match
a converter named ''. Now empty strings are stripped at the route level.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
show_logs() used the 'tail' command which doesn't exist on Windows.
Now reads last N lines and polls for new content in pure Python.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Aligns with all other timestamp fields in the API which use datetime.
Pydantic handles ISO 8601 serialization automatically.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
if (textareaRef.current) {
textareaRef.current.style.height = 'auto'
textareaRef.current.style.height = Math.min(textareaRef.current.scrollHeight, 120) + 'px'
textareaRef.current.style.height = Math.min(textareaRef.current.scrollHeight, 96) + 'px'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think you should be able to use flex in the component rather than having a useEffect

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a little bit of research on this given the suggestion and this is what I came up with:
useEffect manually resizes the textarea height based on scrollHeight. This can't be done with pure flex. scrollHeight-based useEffect is the standard React pattern for this.

/>

<Button
className={currentView === 'history' ? styles.activeButton : styles.iconButton}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you use :active in the styles

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that does what the name implies ("pressed" rather than "active")

The current code has two near-identical classes (iconButton and activeButton). I merged them into one using a data-active attribute selector.

* - If the app crashes again after "Try again": shows "Reload page" as a
* nuclear option (full page reload).
*/
export class ErrorBoundary extends Component<Props, State> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm why are we not using a functional component here ?

service = get_attack_service()
labels = _parse_labels(label)
# Normalize converter_types: strip empty strings so ?converter_types= means "no converters"
if converter_types is not None:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: why do we need this None ? imo it's better just to have an empty list in order to keep a more consistent type

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None means no filter while [] means filter for "no converters"

Previously _sign_blob_url_async returned URLs unchanged if they already
had a query string. This meant stored blob URLs with expired SAS tokens
(e.g. video input pieces carried over from prior conversations) would
never get fresh tokens. Now the old query string is stripped and a fresh
SAS is always generated.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants