Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
368b137
FEAT: Backend APIs for scan results and running scan attacks
richlundeen Nov 7, 2026
8831052
FEAT: Shared scan result building blocks and ScenarioRunPage split
richlundeen Nov 7, 2026
7220834
FEAT: Scan results redesign and live scan attacks in Chat
richlundeen Nov 7, 2026
d961e11
Merge selected main into backend API stack layer
richlundeen Nov 7, 2026
658e947
DOC: Clarify running attack response state fields
richlundeen Nov 7, 2026
59b314a
Merge reviewed scanner backend dependency
richlundeen Nov 7, 2026
343e600
Fix outcome badge names and strengthen findings refresh coverage
richlundeen Nov 7, 2026
94be2ca
Merge reviewed scan foundations dependency
richlundeen Nov 7, 2026
0db2ee1
Merge selected main into scan results redesign
richlundeen Nov 7, 2026
581e20e
Preserve scorer evaluations and scan conversation evidence
richlundeen Nov 7, 2026
92e0fce
Merge pinned upstream for migration graph integration
richlundeen Nov 7, 2026
3a6dcfe
FIX: Merge finding index and HTTP fingerprint migration heads
richlundeen Nov 7, 2026
f4ae18a
Merge corrected backend migration integration into frontend foundations
richlundeen Nov 7, 2026
27c3b15
Merge corrected scan foundations and migration history
richlundeen Nov 7, 2026
365e8a2
Align scan history acceptance fixtures with single and grouped results
richlundeen Nov 7, 2026
d69f177
Merge pinned upstream main into backend APIs
richlundeen Nov 9, 2026
36ca73f
Merge pinned backend APIs into frontend foundations
richlundeen Nov 9, 2026
041171e
Merge pinned frontend foundations into scan results redesign
richlundeen Nov 9, 2026
395ac44
Merge pinned main into scenario results redesign
richlundeen Nov 12, 2026
7907eae
Fix scan details, retry layout, and conversation view contracts
richlundeen Nov 12, 2026
43935ac
Use public attack exports in scenario topology tests
richlundeen Nov 12, 2026
854e10c
Merge main into frontend foundations
richlundeen Nov 12, 2026
8a9977b
Move redesign-only building blocks to the redesign PR
richlundeen Nov 12, 2026
fbf62e6
Merge frontend foundations scope reduction into the redesign
richlundeen Nov 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions doc/gui/0_gui.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,14 +129,14 @@ Groups and objectives show success rates and completion counts separately.
Completed scans offer an optional **Compare baseline and attack** disclosure
below the objective results. It stays collapsed while you review individual attacks.

**Watch live conversation** opens saved messages while an attack runs. This view
is read-only and refreshes saved messages; it has no send controls. **Cancel scan**
stops remaining scan work and keeps saved results. Ordinary manual chats do not
have scan cancellation controls.
After cancellation, saved messages remain available in the read-only chat.
Saving these conversation links does not add attack outcomes or change success rates.

**Rerun** opens the scenario launch page with saved settings filled in. It does
**Watch live conversation** opens a running attack in Chat. While the scan runs it,
Chat is read-only, shows **Attack in progress** in place of the prompt bar, and
refreshes its messages and conversations; when the attack finishes, Chat shows its
saved result on the same page. **Cancel** stops remaining scan work and keeps saved
results; saved messages remain available in the read-only chat. Opening these
conversations does not add attack outcomes or change success rates.

**Rerun with changes** opens the scenario launch page with saved settings filled in. It does
not start work or change the original run. Review or edit the settings before
launching. Recorded target/scorer identities and the scenario version are checked.
Settings that cannot be restored, including older records without a required identity,
Expand Down
55 changes: 53 additions & 2 deletions frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,59 @@ rows and keyboard focus. Selected conversation URLs, return-to-scan links and
the originating History filters survive paging and reloads.
On narrow screens, the saved viewer scrolls as one surface so loaded evidence
and continuation controls remain reachable without squeezing the transcript.
Selecting another saved conversation starts at its beginning; continuation
keeps the current reading position.
There, selecting another saved conversation starts at its beginning;
continuation keeps the current reading position.

## Scan results and live scan attacks

A scan's run page shows its Operation as parent context above the title. Errors
and scan-level notices (paused updates, overload, an incomplete plan, unmatched
executions) stay explicit; a single attack's own error is not repeated as a run
error. While the scan is active, the progress area shows elapsed time, an
estimate of the time remaining, and **View live attacks**. When the scan
finishes, the progress area collapses in place without moving the results.

A run of one atomic attack shows that attack directly as one card, from
preparing through completion. Other runs list atomic attack groups, datasets
and objectives, and the scenario configuration; each result row opens the
attack details. Attack details list the objective and outcome (with its score),
Comment thread
richlundeen marked this conversation as resolved.
Resolved
the technique, objective scorer and dataset identities, and any findings that
link the attack, followed by its conversation and **Attach to findings**.

Every scan conversation opens in Chat at
`/attack/chat/{attack_id}/conversations/{conversation_id}?scenarioResultId={run_id}`,
whether the attack is still running or finished. Older
`/scanner/history/{run_id}/conversations/{conversation_id}` links redirect there.
While a scan is running the attack, Chat replaces the prompt bar with an
**Attack in progress** bar and a link back to the scan, keeps the attack
read-only, and refreshes its metadata, transcript, and conversation list at the
scan's polling interval. A failed refresh keeps the loaded messages and offers
**Retry**. When the attack finishes, Chat reloads its saved result in place on
the same URL.

An interrupted scan can retain recorded conversations without a saved attack
result. Chat verifies that run, attack and conversation together and reads the
stored evidence through the run-scoped API, including after reload or an older
scan link. These conversations stay read-only; no result, score, main
conversation or message count is invented. Result-dependent actions remain
unavailable.

Live and manual transcripts follow new messages only while the reader is within
48 px of the end; scrolling further up keeps the reading position. Historical
saved evidence keeps its bounded paging and narrow-screen reading behavior
described above.

**Evaluation metrics** in attack details shows the official evaluation quality
of the recorded objective-scorer configuration, including configurations no
longer in the registry. This is separate from the individual attack score in
the outcome popover and the scan's attack-success rate. Missing evaluation
metrics have an explicit unavailable state and a link to the scorer-metrics guide.

The Scorer Registry keeps its compact comparison table; each scorer's
**Evaluation metrics** opens the complete supplied metrics, including standard
errors and sample counts. Runtime identity and behavioral evaluation identity
remain separate. Registry names are not used to substitute metrics for a
recorded run.

## Dataset catalog

Expand Down
23 changes: 15 additions & 8 deletions frontend/e2e/scenario-hierarchy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ test('browses compound results in the existing attack-group table with stable to
await expect(table.getByText('Step 1', { exact: true })).toBeVisible()
await expect(row('#2')).toContainText('Unresolved reference')
await expect(row('#4')).toContainText('Unresolved reference')
await expect(row('Step 5').getByRole('cell').nth(2)).toHaveText('True')
await expect(row('Step 5').getByRole('cell', { name: 'Attack outcome', exact: true })).toHaveText('True')
await expect(section.getByText('Showing 200 of 520 saved child positions')).toBeVisible()

for (const shown of [400, 520]) {
Expand Down Expand Up @@ -348,12 +348,13 @@ test('browses compound results in the existing attack-group table with stable to
await expect(section.getByText('Nested step 2', { exact: true })).toBeVisible()
expect(await totals.textContent()).toBe(before)

await section.getByRole('button', { name: 'Open details for #5 sequential: Step 5' }).click()
const detailsRow = section.getByRole('row', { name: 'View attack details for #5 sequential: Step 5' })
await detailsRow.click()
const dialog = page.getByRole('dialog', { name: 'sequential' })
await expect(dialog.getByText('Step 5', { exact: true })).toBeVisible()
await dialog.getByRole('button', { name: 'Close', exact: true }).click()
await dialog.getByRole('button', { name: 'Close attack details', exact: true }).click()
await expect(page.getByRole('dialog')).toHaveCount(0)
await expect(section.getByRole('button', { name: 'Open details for #5 sequential: Step 5' })).toBeFocused()
await expect(detailsRow).toBeFocused()

await page.getByRole('heading', { name: 'Atomic attack groups', exact: true }).evaluate((heading) =>
heading.scrollIntoView({ block: 'start' }),
Expand All @@ -371,8 +372,8 @@ test('opens an actual saved child chat and retains the existing scenario breadcr
const section = page.getByRole('region', { name: 'Atomic attack groups', exact: true })
await expect(section.getByRole('button', { name: 'Collapse attacks in Sequential' })).toHaveAttribute('aria-expanded', 'true')
await section.getByRole('button', { name: 'Expand children of sequential: Sequential parent' }).click()
const row = section.getByRole('row', { name: 'View conversation for #5 sequential: Step 5' })
const chatLink = row.getByRole('link', { name: 'Step 5', exact: true })
const row = section.getByRole('row', { name: 'View attack details for #5 sequential: Step 5' })
const chatLink = row.getByRole('link', { name: 'Conversation', exact: true })
await expect(chatLink).toHaveAttribute('href', new RegExp(`\\?scenarioResultId=${scenarioResultId}$`))
await chatLink.click()

Expand Down Expand Up @@ -403,7 +404,7 @@ test('opens an actual saved child chat and retains the existing scenario breadcr
await expect(page).toHaveURL(run)
const groups = page.getByRole('region', { name: 'Atomic attack groups', exact: true })
await expect(groups.getByText('2 groups, 524 executions', { exact: true })).toBeVisible()
const single = groups.getByRole('row', { name: 'View conversation for single: Single-turn attempt' })
const single = groups.getByRole('row', { name: 'View attack details for single: Single-turn attempt' })
const collapseSingle = groups.getByRole('button', { name: 'Collapse attacks in Single turn' })
await expect(collapseSingle).toHaveAttribute('aria-expanded', 'true')
await expect(single).toBeVisible()
Expand All @@ -412,6 +413,9 @@ test('opens an actual saved child chat and retains the existing scenario breadcr
await groups.getByRole('button', { name: 'Expand attacks in Single turn' }).click()
await single.focus()
await page.keyboard.press('Enter')
const singleDetails = page.getByRole('dialog', { name: 'single' })
await expect(singleDetails).toBeVisible()
await singleDetails.getByRole('link', { name: 'Open conversation' }).click()
await expect(page.getByText('Stored example response for Single-turn attempt. This is offline sample data.', { exact: true })).toBeVisible()
await page.getByRole('link', { name: 'Back to scan', exact: true }).click()
await expect(page).toHaveURL(run)
Expand Down Expand Up @@ -708,8 +712,11 @@ test('owns pending continuations across rapid selection, attack navigation and u
await expect(page).toHaveURL(run)
const groups = page.getByRole('region', { name: 'Atomic attack groups', exact: true })
await expect(groups.getByRole('button', { name: 'Collapse attacks in Single turn' })).toHaveAttribute('aria-expanded', 'true')
await groups.getByRole('row', { name: 'View conversation for single: Single-turn attempt' }).focus()
await groups.getByRole('row', { name: 'View attack details for single: Single-turn attempt' }).focus()
await page.keyboard.press('Enter')
const details = page.getByRole('dialog', { name: 'single' })
await expect(details).toBeVisible()
await details.getByRole('link', { name: 'Open conversation', exact: true }).click()
await expect(page.getByText('Stored example response for Single-turn attempt. This is offline sample data.', { exact: true })).toBeVisible()
await controlSavedRead(page, 'release_messages')
expect((await unmounted).status()).toBe(200)
Expand Down
Loading
Loading