Skip to content

feat(attack-path): causal chain view, finding-panel correctness and drawer polish (#6647) - #6964

Merged
Seb-MIGUEL merged 18 commits into
mainfrom
smi/attack-path-followups
Jul 27, 2026
Merged

feat(attack-path): causal chain view, finding-panel correctness and drawer polish (#6647)#6964
Seb-MIGUEL merged 18 commits into
mainfrom
smi/attack-path-followups

Conversation

@Seb-MIGUEL

@Seb-MIGUEL Seb-MIGUEL commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Front follow-ups on the Attack Path view (behind the ATTACK_PATH flag), extending #6920. Groups the execution-drawer polish with the causal-chain view work, finding-panel correctness fixes, and the front-side alignment with the backend PRs merged in the meantime (#6912 expectations/terminal data, #6972 SMB shares as the native file type).

Execution drawer & findings list

  • Findings tab in the execution drawer (between "Execution details" and "Remediation"): renders the shared FindingList scoped to the inject (searchDistinctFindingsForInjects), so it works for both endpoint and injector-contract executions. Shown only when the execution carries an inject id.
  • FindingList gains compact + hiddenFields props (defaults unchanged, existing usages untouched): compact hides the search/filters/pagination top bar; hiddenFields hides columns. In the narrow drawer it drops the asset-groups and first-seen / last-seen columns, leaving Type / Value / Assets.
  • Compact page size raised + "showing X of N" note so scan findings are never silently hidden when the list is truncated.
  • Wider default drawer (460 → 560px) so all four tabs (incl. the previously-clipped Remediation) and the findings table fit; still drag-resizable.
  • Empty state is now truly centered in the graph area (position: absolute; inset: 0, 88px icon, h6 message) instead of a small block stuck at the top.

Causal chain view (legibility)

  • Collapse duplicate causal edges on hub endpoints: a key like share_name IS_NOT_NULL matches every produced finding of the type, which stacked N identical "Triggered …" labels over the consumer. Draw a single causal edge per (consumer, label), anchored to the nearest matching finding, so a one-asset/many-findings hub reads as cleanly as a linear chain.
  • Draw the causal edge for IS_NOT_NULL events (any produced finding of the reconciled type satisfies the event).
  • Finding value labels: show the full value flowing rightward instead of truncating, and give expanded findings enough vertical room so labels don't overlap the node above.
  • Correct injector-highlight attribution and stabilize endpoint order on live refresh.

Finding panel correctness (chain & focused views)

  • "Discovered on" shows the real endpoint (host / IP) instead of the literal word "Endpoint": the finding carries its origin endpoint node id, resolved against the full graph (chain mode has no focusedEndpoint to fall back on).
  • Clicking a finding always updates the panel: the resolution no longer bails when the collapsed-graph lookup misses, so clicking a share no longer left the previous portscan's panel on screen.
  • "Discovered by" resolves the real producer even for id-escaped values: the backend escapes \ and | in node ids, so a share value like \\host\NETLOGON never matched a rebuilt NODE_FINDING|type|value. Match producers by the node's actual / canonical id instead — fixed in both the chain and focused views.
  • A finding's highlight is scoped to its real production path: skip forward causal edges in the upstream walk and restrict to the producing injector(s), so a plain portscan click no longer lit the whole downstream kill-chain and every injector on the shared endpoint.
  • Injector panel findings come from the full graph (per-execution findingsNodeIds): shares now show for an injector that only produced shares, which the drawer category endpoint missed (it maps filesfile).
  • Producing-action list falls back to the full graph while the endpoint feed loads, removing the brief "no producing action" flash.

Alignment with merged backend PRs

i18n

  • Restored the action/arsenal locale keys that an earlier rebase resolution had dropped, so the i18n checker passes.

check-ts and eslint clean; attack-path unit tests green. Verified live against a locally rebuilt backend carrying #6912 + #6972.

Related issue: #6647

@github-actions

Copy link
Copy Markdown

/review docs

📖 Docs Reviewer triggered automatically.

3 files changed — 3 functional, 0 documentation.

⚠️ No documentation files modified. Agent will check if doc updates are needed.

@Seb-MIGUEL
Seb-MIGUEL requested a review from laugiov July 27, 2026 06:46
@Filigran-Automation Filigran-Automation added the filigran team Item from the Filigran team. label Jul 27, 2026
@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 27.11864% with 86 lines in your changes missing coverage. Please review.
✅ Project coverage is 44.93%. Comparing base (514a000) to head (13f0513).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...ns/simulation/attack_path/SimulationAttackPath.tsx 18.29% 64 Missing and 3 partials ⚠️
...ront/src/admin/components/findings/FindingList.tsx 0.00% 11 Missing ⚠️
...simulation/attack_path/attack-path-flow-helpers.ts 80.00% 1 Missing and 3 partials ⚠️
...ation/attack_path/ExecutionResultTerminalPanel.tsx 25.00% 1 Missing and 2 partials ⚠️
...tions/simulation/attack_path/nodes/FindingNode.tsx 0.00% 1 Missing ⚠️

❌ Your patch check has failed because the patch coverage (27.11%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage.
❌ Your project check has failed because the head coverage (7.55%) is below the target coverage (80.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files
@@             Coverage Diff              @@
##               main    #6964      +/-   ##
============================================
+ Coverage     44.57%   44.93%   +0.35%     
  Complexity     9327     9327              
============================================
  Files          2681     2681              
  Lines         80357    80440      +83     
  Branches      12036    12066      +30     
============================================
+ Hits          35823    36144     +321     
+ Misses        41918    41675     -243     
- Partials       2616     2621       +5     
Flag Coverage Δ
backend 67.82% <ø> (ø)
e2e 16.95% <ø> (-0.13%) ⬇️
frontend 7.55% <27.11%> (+1.06%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@laugiov laugiov 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.

One thing worth confirming before merge: in compact mode disablePagination hides the pager, but the fetch still uses the default page size of 20. An attack-path inject from a scan (nmap, nuclei, netexec) often has more than 20 distinct findings, so the drawer would show 20 and silently hide the rest, with no "20 of N" and no way to reach the others. totalElements is already tracked in PaginationComponentV2, so a small "showing 20 of N" with a link to the inject's full Findings list (or a larger compact page size) would keep it from reading as "this inject has 20 findings". Is capping at 20 intended here?

@Seb-MIGUEL

Copy link
Copy Markdown
Contributor Author

Good catch — fixed in c756b81. In compact mode I now raise the page size to 100 (covers the vast majority of scans without a pager), and when a run still produces more, a "Showing X of N findings — open the inject to see them all." note appears (total tracked from totalElements). So the drawer never silently reads as "this inject has N findings", and the full list stays reachable via the inject (Action details → its Findings tab).

@Seb-MIGUEL
Seb-MIGUEL force-pushed the smi/attack-path-followups branch 2 times, most recently from e7428e0 to 11aed7a Compare July 27, 2026 13:59
@Seb-MIGUEL Seb-MIGUEL changed the title feat(attack-path): findings tab, centered empty state, wider drawer and compact finding list (#6647) feat(attack-path): causal chain view, finding-panel correctness and drawer polish (#6647) Jul 27, 2026
…ndpoint order on refresh, cap finding value labels to avoid overlap (#6647)
…ed, so scan findings are never silently hidden (#6647)
…r legibility (#6647)

A consumed key like `share_name IS_NOT_NULL` matches every produced finding of the type, so the chain view drew one identical "Triggered …" edge per finding — stacking N labels over the consumer node and making a single hub endpoint (one asset, many findings) far less readable than a linear multi-endpoint chain.

Draw a single causal edge per (consumer, label), anchored to the matching finding nearest the consumer to minimise crossing. A hub endpoint is now as legible as a linear chain.
…6647)

The earlier rebase resolved the lang conflicts with `checkout --theirs`, which replaced each locale file wholesale with the branch's version and dropped the keys main had added (the actions/arsenal rename). Restored every locale from main and re-added only the compact-findings key, so the i18n checker passes again.
…n chain mode (#6647)

Clicking a finding in the causal-chain view had three defects: the panel's 'Discovered on' fell back to the literal word 'Endpoint' (no pathFinding/focusedEndpoint in chain mode); clicking a finding whose endpoint missed the collapsed-graph lookup left the previous finding's panel on screen (a share still showed the last portscan); and the producer list was empty for any value the backend id-escapes — a share like \host\NETLOGON encodes with doubled backslashes, so the rebuilt NODE_FINDING|type|value never matched the executions' findingsNodeIds.

Carry the finding's origin endpoint node id on findingDetail and resolve the label from either graph; resolve the endpoint from the full graph too and never bail in chain mode; and match producers by the node's actual (already-escaped) id instead of rebuilding it raw.
…path in chain mode (#6647)

Clicking a finding walked every edge upstream, including the forward causal ("Triggered …") edges, so a plain portscan click lit the whole downstream kill-chain (the consumer injector and its shares) and every injector that merely reached the shared endpoint. Skip causal edges in the walk and, when the finding's producing injector(s) are known from its executions, don't light the others — so the highlight matches the panel's single producer.
…ve the producing-action flash (#6647)

Two chain-mode panel glitches: clicking an injector read 'No findings on this endpoint' whenever it only produced shares, because the injector-findings lookup went through the drawer category endpoint which maps files→"file" and never returns type "share"; and clicking a finding flashed 'no producing action' until the endpoint feed finished loading. Source injector findings from the full graph's per-execution findingsNodeIds (covers every type), and fall back to the full graph's executions for the producing-action list until the feed loads.
…panel (#6647)

#6912 now persists per-execution prevention/detection/vulnerability statuses, which the backend aggregates onto each finding node's verdicts (worst-of across producers). Read those instead of the placeholder; the DTO already serialises the exact success/failed/unknown labels the panel expects. Also realign the dedup test to the native 'file' share type (#6972 renamed SMB shares from 'share').
…s drawer (#6647)

The drawer is narrow, so the six-column findings table read cramped. Hide first-seen and last-seen (alongside the already-hidden asset groups) so only Type / Value / Assets show.
…ia its canonical node id (#6647)

The focused (chokepoint) view rebuilt the finding node id as NODE_FINDING|type|value with the raw value, which never matched the executions' findingsNodeIds for backslash/pipe values — so a share read 'Discovered by (0)'. Resolve the finding's canonical id from the full graph (type-agnostic, correctly escaped) and match producers on that, same as the chain view.
…nder a finding in the drawer (#6647)

The category drawer printed item.endpointKey (a raw asset uuid) under each finding value. Resolve it to the endpoint's friendly hostname via endpointLabelByRef, and hide the line when it can't be resolved so no bare id ever surfaces.
…ame lookup (#6647)

item.endpointKey is optional, so passing it straight to endpointLabelByRef.get() broke check-ts. Resolve the friendly hostname once with a guard per row.
@Seb-MIGUEL
Seb-MIGUEL force-pushed the smi/attack-path-followups branch from 89710c8 to 13f0513 Compare July 27, 2026 15:58
@Seb-MIGUEL
Seb-MIGUEL merged commit 3e717ad into main Jul 27, 2026
38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

filigran team Item from the Filigran team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants