Skip to content

FEAT: Scan results redesign and live scan attacks in Chat (part 3) - #4032

Open
Richard Lundeen (richlundeen) wants to merge 18 commits into
microsoft:mainfrom
richlundeen:richlundeen-scanner-stack-3-redesign-and-live-chat
Open

Richard Lundeen (richlundeen) wants to merge 18 commits into
microsoft:mainfrom
richlundeen:richlundeen-scanner-stack-3-redesign-and-live-chat

Conversation

@richlundeen

@richlundeen Richard Lundeen (richlundeen) commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #4031 (which is stacked on #4030) — review only the commits after 343e600.

Description

Redesigns the scan results page around the scanner prototype and makes Chat the single conversation view for scan attacks, whether they are still running or finished.

  • Run page: the Operation shows as parent context above the title; the top summary grid is gone. Errors and notices stay explicit, but a single attack's own error is not repeated as a run error. The progress area has elapsed time, a stable ETA line ("Estimating time remaining"), and a View live attacks (N) popover. When the scan finishes it collapses in place (grid-rows/opacity, reduced-motion aware, hidden and inert), so results never jump.
  • Single atomic attack runs (e.g. technique.execute) show that attack as one framed card that stays mounted from preparing through running to completed. Multi-group runs keep the groups, datasets/objectives and configuration sections.
  • One attack details component (inline card and dialog, via ScenarioAttackDetails): Objective and Outcome (score popover, rationale, timing line, error), Technique / Objective scorer / Dataset identities, linked Findings, then Open conversation / Watch live conversation and Attach to findings (defaults to the run's Operation). Findings loading/read failures and Retry are explicit instead of looking like an empty successful list. The dialog has a single X close and the Operation link above its title.
  • Result tables are visually headerless with no result-type column, while retaining semantic column headers for assistive technology. Rows open attack details, outcome badges open the score, and rows show the technique shorthand (also in the objective dialog).
  • Chat for live attacks: when the loaded attack is in_progress, useLiveAttackMetadata polls metadata at the scan interval while Chat re-reads the transcript and conversation list. An Attack in progress bar replaces the prompt bar. When the attack finishes, its saved result reloads in place on the same URL without dropping the loaded transcript prefix. A failed refresh keeps loaded messages and offers Retry. Conversation-list polling avoids overlapping reads; switching branches never shows the previous branch.
  • MessageList opens live/manual transcripts at the latest message and follows new messages only within 48 px of the end. Historical saved pages preserve their reading/paging position; an already watched transcript keeps its scroll owner through saved completion. Live additions retain polite log announcements without announcing an entire initial saved history.
  • Scorer evaluation metrics remain discoverable: Registry > Scorers > Evaluation metrics exposes the complete available evaluation, while Objective scorer > Evaluation metrics in scan attack details uses the recorded configuration's metrics, even if it is no longer registered. Both reuse one renderer and distinguish scorer quality from an attack score or scan success rate. See the screenshots below.
  • Routing: every scan conversation link targets /attack/chat/:attack/conversations/:cid?scenarioResultId=. /scanner/history/:run/conversations/:cid redirects there (ScanConversationRedirect). Interrupted scans can still show verified recorded conversation evidence read-only, without fabricating a saved attack result. ScenarioConversationPage, ScenarioRunOverview and ObjectiveScorerDetails are removed; the compact shared metrics disclosure replaces the old expanded scorer card.
  • Pure layout rules live in scenarioRunLayout.ts: single-attack detection, live attack grouping, interrupted conversations, and conversation → attack lookup.

Layer 2 files touched where required: AtomicAttackGroupsSection (technique names, shared execution count, and restored metric column styling), ScenarioRunHeader (Operation link, "Rerun with changes"), ScenarioRunNotices, ScenarioRunProgress, AttackDetailsDialog, and ScenarioRunPage.styles.

Verified in a browser against a real local backend (in-memory DB, slow echo target, substring scorer), at 1360 px and 390 px: technique.execute live → saved in Chat on the same URL; a three-attack airt.rapid_response run whose results heading stayed fixed from 0 → 1 → 2 completed; findings and Operation links; no horizontal overflow; zero console errors on the production build.

Before and after

Before is #4031; after is this PR. Both were captured from production builds against the same disposable backend: an in-memory database, an offline echo target that answers after 20 s, and a substring objective scorer. No providers were called.

Running scan with several attacks

Before After
Before: running scan with summary grid and an Executing block above the results After: running scan with one progress area and View live attacks
The summary grid and a separate Executing block sit above the results. One progress area replaces both, so the page no longer grows or jumps as attacks start and finish.

After: View live attacks popover listing the running attack and its live conversation link

After — live attacks: running attacks open from the progress area's View live attacks popover, with direct live-conversation links, instead of a section inserted above the results.

Finished scan with several attacks

Before After
Before: finished scan with column headers, result type and Details buttons After: finished scan with compact rows and technique names
Results show column headers, a result-type column, and separate Details buttons. Headerless result rows show the technique under each objective and open attack details; the progress area has collapsed.

Single atomic attack (technique.execute) while running

Before After
Before: running single-attack scan with summary grid and Executing block After: running single-attack scan showing the attack card in progress
A summary grid and Executing block represent the running attack. The attack card is shown from the start with a live-conversation link; the same card fills in when the attack completes.

Single atomic attack (technique.execute) finished

Before After
Before: single-attack scan shown as a one-group table After: single-attack scan shown as one attack card with findings
The finished attack appears in a one-row group table. The attack is shown directly as one card, including linked findings.

Attack details

Before After
Before: attack details dialog with metric grid and nested technique identity After: attack details dialog with Objective, Outcome, components and findings
The dialog has a metric grid and nested technique identity. One aligned label column, the score behind the outcome badge, identity disclosures, linked findings, and Attach to findings; a single close control with the Operation above the title.

Watching a running attack

Before After
Before: separate scan conversation page for a running attack After: Chat showing the running attack with the Attack in progress bar
Scans use a separate read-only conversation page. The same link opens Chat with an Attack in progress bar in place of the prompt bar.

Narrow screens (390 px)

Before After
Before: single-attack scan at 390 px After: single-attack scan at 390 px
The single-attack scan retains its summary-and-table layout at 390 px. The direct attack card fits the same narrow viewport without horizontal overflow.

Recordings

l3-live-conversation.webm

After — running attack in Chat until it finishes: the transcript and conversation list refresh while the scan runs; when the attack is saved, Chat reloads it in place on the same URL.

l2-live-conversation.webm

Before — the same flow: the running attack uses the separate scan conversation page.

l3-multi-progress.webm

After — a three-attack scan from start to finish: the results stay in place as attacks complete, and the progress area collapses when the scan finishes.

Scorer evaluation metrics

The cleaner attack card does not discard scorer-quality information. The registry retains its comparison columns and adds a compact full-evaluation disclosure; the scan's Objective scorer offers the same disclosure for the recorded configuration, independent of today's registry. Available standard errors, sample counts and other supplied fields remain readable, with explicit unavailable states and documentation. Canonical runtime identity stays separate from behavioral evaluation identity. Meaningful zero values are preserved; an unmeasured zero duration is not presented as a measured score time.

Scorer metrics are retained, with two places to find them

The existing PR description already showcases the scan redesign, attack details, findings, live Chat, mobile layouts, and before/after recordings. This follow-up shows the scorer-quality information that should not disappear behind that redesign.

Information Where to find it
One attack's score, scorer, category and rationale Open its outcome badge
A scan's aggregate attack-success rate Its result-group/objective summaries
Scorer evaluation quality Registry > Scorers > Evaluation metrics, or Objective scorer > Evaluation metrics in scan attack details

Image provenance: captures are from 581e20ecd39ad51807b496a4b93f88aae388d2c6; the metrics production code and these image bytes are unchanged at published 365e8a26f954644d8449f19e0184a66c57588337. All metric values are synthetic illustrations, NOT MEASURED scorer performance. The registry screenshot uses the actual app/backend with synthetic evaluation data. The other two are clearly labeled previews of actual production components under normal providers with synthetic props, not backend-write/provider/end-to-end evidence.

Registry: compare the main metrics, then open the full evaluation

Actual scorer registry with complete evaluation metrics and illustrative NOT MEASURED values

The compact comparison columns stay visible. Evaluation metrics reveals all supplied fields, including standard errors, sample counts, available dataset details, documentation, and the separate behavioral evaluation identity. Canonical runtime identity remains a separate disclosure.

Float-scale scorers use the same disclosure, with meaningful zeros preserved

Actual float-scale metrics component preview preserving zero MAE and unavailable duration

MAE, its standard error, agreement and extra supplied fields remain available. A real zero MAE is displayed; an unmeasured zero duration remains unavailable rather than becoming a fabricated timing measurement.

Scan details: metrics for the recorded scorer, even when it is no longer registered

Recorded-scorer evaluation metrics remain accessible directly from scan attack details

The compact Evaluation metrics control next to Objective scorer reads the run's recorded scorer data; it does not depend on today's registry alias or silently substitute a different configuration. Missing evaluation data has an explicit unavailable explanation and a documentation link.

- GET /api/attacks/{id}/findings lists findings linked to an attack across Operations,
  backed by a new FindingEvidenceEntries.attack_result_id index (Alembic b5d7f9a1c3e6).
- Attack read APIs (summary, metadata, conversations, messages) resolve scan attacks that
  are still running through a read-only LiveAttackLookup provided by ScenarioRunService.
  Saved results always win; summaries/metadata expose in_progress and scenario_result_id.
- ScenarioActiveConversation.conversation_ids lists every objective conversation so far.
- ScenarioProgressScore carries score_category for progress results and hierarchy nodes.
- Frontend types and attacksApi.getAttackFindings for later layers.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Split ScenarioRunPage into section components and hooks, and add the shared
blocks the scan results redesign builds on: OperationParentLink, ScanStatusBadge,
OutcomeBadge progress labels and score popover, ScoreDetails over a narrow
ScoreSummary, DetailFacts, IdentityDisclosure, AttackActivity,
formatAttemptTiming, useAttackFindings, and AttachToFindingsButton.
Existing pages adopt them only where output is unchanged.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- Run page: Operation as parent context above the title, no summary grid,
  explicit notices with a single attack's own error not repeated as a run error,
  and a progress area (elapsed, ETA, "View live attacks") that collapses in place.
- A run of one atomic attack shows that attack as one framed card from preparing
  through completion; other runs keep groups, objectives and configuration.
- One attack details component (inline card and dialog): Objective and Outcome
  with score, technique/scorer/dataset identities, findings when present,
  conversation link and Attach to findings. Result rows open these details.
- Chat is the single conversation view for running and finished scan attacks:
  in-progress bar, polling of metadata, transcript and conversations, and an
  in-place reload when the attack finishes. Old scan conversation URLs redirect.
- MessageList follows new messages only while the reader is near the end.
- Remove ScenarioConversationPage, ScenarioRunOverview and ObjectiveScorerDetails.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings November 7, 2026 03:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Unresolved issues cause 404s when opening cancelled conversations, hide recent messages when long live transcripts switch to saved paging, and discard conversation-list responses when reads exceed the polling interval.

3 open issues
What changed in this PR

Redesigns PyRIT’s scan results experience and makes Chat the shared viewer for running and completed scan attacks, building on the backend and shared-component work in #4030 and #4031.

Changes:

  • Reorganizes scan results around attack details, Operation context, progress, and linked findings.
  • Adds live Chat polling, completion transitions, and redirects from older scan conversation URLs.
  • Adds supporting API contracts, finding lookups, shared presentation components, tests, and documentation.
File Description
tests/​unit/​memory/​test_score_participation_migration.py Allows a newer migration head.
tests/​unit/​memory/​test_findings.py Tests cross-Operation attack findings.
tests/​unit/​memory/​test_finding_evidence_attack_index_migration.py Tests index migration and portability.
tests/​unit/​memory/​memory_interface/​test_interface_scenario_progress.py Verifies score categories in progress.
tests/​unit/​backend/​test_scenario_resume.py Tests live-to-saved attack reads.
pyrit/​models/​scenario_progress.py Adds categories and live conversation IDs.
pyrit/​memory/​memory_models.py Declares the attack-evidence index.
pyrit/​memory/​memory_interface.py Adds finding lookup and shared reference paging.
pyrit/​memory/​alembic/​versions/​b5d7f9a1c3e6_index_finding_evidence_attack_result.py Creates the attack-evidence index.
pyrit/​backend/​services/​scenario_run_service.py Exposes live attack lookup.
pyrit/​backend/​services/​saved_conversation_read_service.py Accepts injectable conversation references.
pyrit/​backend/​services/​operation_service.py Returns findings linked to attacks.
pyrit/​backend/​services/​live_attack_reader.py Defines read-only live attack projections.
pyrit/​backend/​routes/​attacks.py Adds the attack findings endpoint.
pyrit/​backend/​README.md Documents live reads and findings.
pyrit/​backend/​models/​operations.py Defines attack findings responses.
pyrit/​backend/​models/​attacks.py Adds live status and scan provenance.
pyrit/​backend/​mappers/​attack_mappers.py Maps live status and provenance.
frontend/​src/​utils/​scoreSummary.ts Normalizes score presentation data.
frontend/​src/​utils/​scoreSummary.test.ts Tests score normalization.
frontend/​src/​utils/​routeParams.ts Removes the old conversation route helper.
frontend/​src/​types/​index.ts Extends live attack and findings contracts.
frontend/​src/​services/​api.ts Adds attack findings requests.
frontend/​src/​services/​api.test.ts Tests findings requests and failures.
frontend/​src/​hooks/​useScenarioRunControls.ts Extracts rerun, cancel, and resume controls.
frontend/​src/​hooks/​useScenarioRunAttackDetails.ts Manages routed details and focus return.
frontend/​src/​hooks/​useLiveAttackMetadata.ts Polls running attack metadata.
frontend/​src/​hooks/​useLiveAttackMetadata.test.tsx Tests polling lifecycle and recovery.
frontend/​src/​hooks/​useAttackFindings.ts Loads and refreshes linked findings.
frontend/​src/​hooks/​useAttackFindings.test.tsx Tests findings loading and isolation.
frontend/​src/​components/​ScenarioSuites/​ScenarioSuiteExecutionPage.tsx Uses shared scan status badges.
frontend/​src/​components/​Scenarios/​ScenarioTechniqueSection.tsx Reuses identity disclosure styles.
frontend/​src/​components/​Scenarios/​ScenarioRunTiming.tsx Keeps an estimating-time placeholder.
frontend/​src/​components/​Scenarios/​ScenarioRunSavedConversations.tsx Links interrupted conversations to Chat.
frontend/​src/​components/​Scenarios/​ScenarioRunProgress.tsx Combines progress, timing, and live attacks.
frontend/​src/​components/​Scenarios/​ScenarioRunPage.styles.ts Styles the redesigned run layout.
frontend/​src/​components/​Scenarios/​ScenarioRunObjectivesSection.tsx Presents datasets and objective totals.
frontend/​src/​components/​Scenarios/​ScenarioRunNotices.tsx Consolidates errors and run notices.
frontend/​src/​components/​Scenarios/​scenarioRunNavigation.ts Centralizes run back-link selection.
frontend/​src/​components/​Scenarios/​scenarioRunNavigation.test.ts Tests back-link destinations.
frontend/​src/​components/​Scenarios/​scenarioRunLayout.ts Defines attack layout and lookup rules.
frontend/​src/​components/​Scenarios/​scenarioRunLayout.test.ts Tests layout and conversation lookup.
frontend/​src/​components/​Scenarios/​ScenarioRunHeader.tsx Adds Operation context and run controls.
frontend/​src/​components/​Scenarios/​ScenarioRunDialogs.tsx Updates objective and cancellation dialogs.
frontend/​src/​components/​Scenarios/​ScenarioRunConfigurationSection.tsx Extracts saved configuration disclosure.
frontend/​src/​components/​Scenarios/​ScenarioResultHierarchy.tsx Removes outdated interaction guidance.
frontend/​src/​components/​Scenarios/​ScenarioResultHierarchy.test.tsx Updates row interaction expectations.
frontend/​src/​components/​Scenarios/​scenarioHierarchy.ts Centralizes execution totals.
frontend/​src/​components/​Scenarios/​ScenarioConversationPage.tsx Removes the separate conversation viewer.
frontend/​src/​components/​Scenarios/​ScenarioConversationPage.transitions.test.tsx Removes old viewer transition tests.
frontend/​src/​components/​Scenarios/​ScenarioConversationPage.test.tsx Removes old viewer tests.
frontend/​src/​components/​Scenarios/​ScenarioAttackDetails.tsx Resolves shared scan attack details.
frontend/​src/​components/​Scenarios/​ScanConversationRedirect.tsx Redirects legacy conversation URLs.
frontend/​src/​components/​Scenarios/​ScanConversationRedirect.test.tsx Tests redirects and lookup states.
frontend/​src/​components/​Scenarios/​LiveAttacksPopover.tsx Lists running attacks and Chat links.
frontend/​src/​components/​Scenarios/​AttackExecutionTable.styles.ts Compacts result table styling.
frontend/​src/​components/​Scenarios/​AttackDetailsDialog.tsx Wraps shared details with Operation context.
frontend/​src/​components/​Scenarios/​AttackActivity.tsx Adds a reusable activity indicator.
frontend/​src/​components/​Scenarios/​AttackActivity.test.tsx Tests activity labels.
frontend/​src/​components/​Scenarios/​AttackActivity.styles.ts Styles reduced-motion-aware activity.
frontend/​src/​components/​ScanStatusBadge.tsx Centralizes scan lifecycle badges.
frontend/​src/​components/​ScanStatusBadge.test.tsx Tests status labels and icons.
frontend/​src/​components/​Registry/​RegistryIdentityRow.tsx Reuses shared identity disclosure.
frontend/​src/​components/​Registry/​RegistryIdentityRow.styles.ts Removes extracted disclosure styles.
frontend/​src/​components/​Registry/​IdentityDisclosure.tsx Adds reusable identity JSON disclosure.
frontend/​src/​components/​Registry/​IdentityDisclosure.test.tsx Tests disclosure interaction and focus.
frontend/​src/​components/​Registry/​IdentityDisclosure.styles.ts Styles identity disclosures.
frontend/​src/​components/​OutcomeBadge.tsx Adds pending states and score popovers.
frontend/​src/​components/​OutcomeBadge.test.tsx Tests labels and score interactions.
frontend/​src/​components/​OutcomeBadge.styles.ts Styles pending states and score triggers.
frontend/​src/​components/​Operations/​OperationParentLink.tsx Adds reusable Operation context links.
frontend/​src/​components/​Operations/​OperationParentLink.test.tsx Tests links and missing identities.
frontend/​src/​components/​Operations/​OperationParentLink.styles.ts Styles Operation context links.
frontend/​src/​components/​Home/​Home.tsx Uses shared scan status badges.
frontend/​src/​components/​DetailFacts.tsx Adds aligned detail label/value pairs.
frontend/​src/​components/​DetailFacts.test.tsx Tests description-list semantics.
frontend/​src/​components/​DetailFacts.styles.ts Adds responsive detail layouts.
frontend/​src/​components/​Chat/​ScoreDetails.tsx Extracts shared score details.
frontend/​src/​components/​Chat/​ScoreDetails.test.tsx Tests score detail presentation.
frontend/​src/​components/​Chat/​ScoreDetails.styles.ts Extracts score popover styles.
frontend/​src/​components/​Chat/​MessageList.test.tsx Tests near-end scrolling behavior.
frontend/​src/​components/​Chat/​MessageList.styles.ts Removes extracted score styles.
frontend/​src/​components/​Chat/​FindingEvidenceDialog.tsx Reports completed attachments to callers.
frontend/​src/​components/​Chat/​ConversationPanel.tsx Adds live conversation-list polling.
frontend/​src/​components/​Chat/​ConversationPanel.test.tsx Tests polling recovery and row retention.
frontend/​src/​components/​Chat/​ChatWindow.tsx Integrates live polling and progress UI.
frontend/​src/​components/​Chat/​AttackInProgressBar.tsx Replaces input during live attacks.
frontend/​src/​components/​Chat/​AttackInProgressBar.styles.ts Styles the live attack bar.
frontend/​src/​components/​Chat/​AttachToFindingsButton.tsx Wraps the finding attachment workflow.
frontend/​src/​components/​Chat/​AttachToFindingsButton.test.tsx Tests attachment and focus return.
frontend/​src/​components/​Chat/​AttachToFindingsButton.styles.ts Adds attachment button touch sizing.
frontend/​src/​components/​AttackResults/​ObjectiveScorerDetails.tsx Removes the previous scorer details component.
frontend/​src/​components/​AttackResults/​ObjectiveScorerDetails.styles.ts Removes obsolete scorer styles.
frontend/​src/​components/​AttackResults/​attackAttemptFormatting.ts Adds retry and timing formatting.
frontend/​src/​components/​AttackResults/​attackAttemptFormatting.test.ts Tests retry and timing formatting.
frontend/​src/​components/​AttackResults/​AttackAttemptDetails.styles.ts Styles redesigned attack detail cards.
frontend/​src/​App.tsx Integrates live metadata and legacy redirects.
frontend/​src/​App.test.tsx Tests live-to-saved metadata transitions.
frontend/​src/​App.labels.test.tsx Verifies saved Operation attribution.
frontend/​README.md Documents redesigned results and live Chat.
doc/​gui/​0_gui.md Updates scan viewing and rerun guidance.

Review effort: Balanced

Integrate main 25417ff without rewriting the existing stack history.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Limit running-state guarantees to attack summary and metadata reads without expanding conversation/message schemas or removing score details.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Carry forward the verified running-state documentation correction and the predecessor main-integration lockfile state.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Preserve the visible verdict in score-trigger accessible names, cover saved and pending outcomes plus custom labels, and prove populated findings survive refresh before replacement.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@richlundeen

Copy link
Copy Markdown
Contributor Author

Addressed the three concerns in this review at 581e20ecd39ad51807b496a4b93f88aae388d2c6: preserve the loaded live transcript through saved paging, let slow reference reads settle, and resolve interrupted conversations through verified scan provenance. Focused regressions for all three passed; the individual threads have the source-specific details.

Integrate confirmed CI base c979a97 without rewriting either Alembic branch. The reproduced dual-head conflict is repaired in the following commit before publication.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Preserve both published revision histories and restore singular-head backend startup. Cover fresh, common-parent, either-branch and dual-head upgrades with preserved data, idempotency, downgrade/re-upgrade and SQLite/SQL Server merge semantics.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Carry forward the reviewed no-DDL Alembic merge revision and pinned upstream integration while preserving the foundations repairs and published visual walkthrough.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@richlundeen

Copy link
Copy Markdown
Contributor Author

Regarding the original review: the three independently checked inline causes remain addressed: live-to-saved window retention, non-starving conversation-list polling and owned recorded-only evidence in shared Chat. This summary adds no separate defect.

Preserved at 365e8a26f954644d8449f19e0184a66c57588337 after the corrected backend forward merge and scan-history acceptance-test repairs. CI is observed separately; this is not a CI-pass claim.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Automated review completed. No blocking concerns remain. This review records source coverage; it does not authorize merge.

@richlundeen Richard Lundeen (richlundeen) removed their assignment Nov 7, 2026
@richlundeen Richard Lundeen (richlundeen) changed the title FEAT: Scan results redesign and live scan attacks in Chat FEAT: Scan results redesign and live scan attacks in Chat (part 3) Nov 9, 2026
Integrate microsoft/PyRIT main at 8253694. Preserve live attack reads, findings, score provenance and both migration histories while adopting canonical public imports.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@behnam-o Behnam (behnam-o) self-assigned this Nov 11, 2026

@behnam-o Behnam (behnam-o) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed this layer's own changes (36ca73f..041171e); parts 1 and 2 are reviewed on #4030 and #4031. I didn't find any security issues.

Requesting changes mainly because the Frontend E2E (scenario-hierarchy) job now fails on every run against the new results table. There are also two behavior bugs (clicks in the score popover, and the single-attack layout); the other comments are non-blocking.

This was a static review; I didn't run the code or tests.

Comment on lines 165 to +168
<TableRow
className={styles.resultRow}
tabIndex={0}
aria-label={`${conversationPath ? 'View conversation' : expandable ? 'View child results' : 'View details'} for ${label}`}
aria-label={`View attack details for ${label}`}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[HIGH] The scenario-hierarchy E2E job fails against the new table

frontend/e2e/scenario-hierarchy.spec.ts isn't updated anywhere in the stack, and 3 of its tests (around lines 143, 195-197 and 535) now fail on every retry; the job passes on #4031. The spec still expects rows named "View conversation for …", a chat link named after the step, and the outcome in the third cell, but rows are now "View attack details for …", chat opens from a trailing "Conversation" link, and the outcome is the second cell. Please update the spec to the new rows, columns and links and get the job green; until then it doesn't protect the hierarchy reads this PR changes.

Comment on lines 169 to 172
onClick={(event) => {
if (event.target instanceof Element && event.target.closest('a, button')) return
openResult(event.currentTarget)
onOpenDetails(result, event.currentTarget)
}}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[MEDIUM] Clicks inside the score popover open attack details

This PR always gives the outcome badge a score popover (scoreFallback is always set). Fluent renders the popover in a portal, but React still bubbles its clicks through the component tree to this row's onClick, which only skips links and buttons. So clicking the rationale text inside the popover also opens the details dialog. Could the handler ignore targets outside the row (e.g. !event.currentTarget.contains(event.target as Node)), or the outcome cell stop click propagation?

Comment on lines +16 to +45
export function singleAtomicGroupId(
summary: ScenarioProgressSummary,
hierarchyGroups: readonly ScenarioHierarchyGroup[],
loadedResults: number,
): string | null {
const displayGroups = hierarchyDisplayGroups(summary.display_groups ?? summary.techniques, hierarchyGroups)
const roots = hierarchyGroups.flatMap((group) => group.roots)
const displayGroup = displayGroups[0]
const root = roots[0]
const single = summary.overall.planned === 1
&& runExecutionCount(summary, loadedResults).count <= 1
&& displayGroups.length === 1
&& displayGroup?.atomic_group_ids.length === 1
&& roots.length <= 1
&& (!root || !hasChildReferences(root))
return single ? displayGroup?.atomic_group_ids[0] ?? null : null
}

/** The single attack's attempt: its saved result, otherwise its saved root, otherwise the running attack. */
export function singleAttackAttempt(
atomicGroupId: string,
results: readonly ScenarioProgressResult[],
hierarchyGroups: readonly ScenarioHierarchyGroup[],
activeConversations: readonly ScenarioActiveConversation[],
): ScenarioProgressResult | ScenarioHierarchyNode | ScenarioActiveConversation | null {
return results.find((attempt) => attempt.atomic_group_id === atomicGroupId)
?? hierarchyGroups.find((group) => group.atomic_group_id === atomicGroupId)?.roots[0]
?? activeConversations[0]
?? null
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[MEDIUM] The single-attack layout depends on saved rows, not the plan

Besides planned === 1, singleAtomicGroupId requires runExecutionCount(...) <= 1, which sums producer_counts attempts, so every saved row counts, including superseded ERROR attempts and multi-step children. singleAttackAttempt then prefers any saved row over the running attack, and ScenarioRunPage hides the live-attacks link in this mode. So on a resume after an ERROR, or with a multi-step technique, the card can show the old error or the first child while another attempt runs, and the page switches to the grouped tables once a second row is saved. Could the layout be decided from the plan alone (one planned, non-multi-step unit), show the running attempt while the scan is active, and keep the live link?

formatScoreRationale,
formatTimestamp,
} from './attackAttemptFormatting'
import { formatAttemptTiming, formatScoreRationale } from './attackAttemptFormatting'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[LOW] This leaves ComponentIdentityDetails and formatScore as dead code

This import change, together with deleting ObjectiveScorerDetails, removes the last non-test callers of the ComponentIdentityDetails default export and of formatScore. Only DetailMetric is still used (by ScorerMetricsDetails), so the rest of that component and formatScore can be deleted, or DetailMetric can move next to ScorerMetricsDetails.

Comment thread frontend/README.md
Comment on lines +145 to +146
and objectives, and the scenario configuration; each result row opens the
attack details. Attack details list the objective and outcome (with its score),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[LOW] The backend README still describes the old result rows

This now says each result row opens the attack details, but pyrit/backend/README.md (lines 190-193) still says clicking a row opens chat, conversationless rows expand their children, and there's a separate Details action. Please update that paragraph too.

</MessageBar>
)}
<div hidden={editDraft !== null}>
{inProgress ? <AttackInProgressBar scanPath={liveScanId ? scenarioRunRoutePath(liveScanId) : null} scanState={location.state} />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[LOW] Chat can show two "Back to scan" links that behave differently

When Chat is opened from a scan link (?scenarioResultId=) for a running attack, both the existing toolbar link (around line 2644) and this new in-progress bar link render. The toolbar link goes through onReturnToScan and falls back to { fromScenarioHistory: true }, while this one passes the raw location.state, so from a fresh tab the run page's back link falls back to "Back to scanners". Could the bar link only show when the toolbar link doesn't, or go through the same handler?

Comment on lines +1371 to +1379
/** Null when a recorded-only scan reference has no counted attack-result projection. */
message_count: number | null
last_message_preview?: string | null
created_at?: string | null
}

export interface AttackConversationsResponse {
attack_result_id: string
main_conversation_id: string
main_conversation_id: string | null

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[LOW] Backend mirror types are loosened for a frontend-only reader

message_count and main_conversation_id become nullable here, and ScenarioObjectiveScorerMetrics (line 2350) now accepts any extra key, but the backend still declares ConversationSummary.message_count: int, AttackConversationsResponse.main_conversation_id: str, and six fixed scorer metrics. The nulls only exist for App's recorded-conversation reader. Could that reader get its own frontend-only type, so these keep mirroring the backend exactly?

Comment on lines +138 to +139
nameContent={<ScorerEvaluationDetails name={scorer.scorer_registry_name} metrics={scorer.metrics}
scoreType={scorer.identifier.scorer_type} evaluationIdentity={scorer.evaluation_identifier} />}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[LOW] The Registry evaluation-metrics popover is outside this PR's scope

This adds new Registry functionality (all metrics plus the behavioral evaluation identity, with README text and tests) that neither the results redesign nor live Chat needs; scan details already have their own recorded-scorer popover. Could it go in a separate PR?

if (overall) {
const eta = getEtaMilliseconds(run, overall, now)
return eta === null ? null : <Text>About {formatDuration(eta)} left</Text>
return <Text>{eta === null ? 'Estimating time remaining' : `About ${formatDuration(eta)} left`}</Text>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[LOW] "Estimating time remaining" never resolves for some runs

getEtaMilliseconds returns null while completed is 0 or planned is null, so a single-attack run, or one without a known plan, shows "Estimating time remaining" for its whole duration; part 2 hid this line instead. Could it show only when an estimate is possible, and otherwise say it's unavailable?

This branch has not been deployed

No deployments
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.

3 participants