Skip to content

Scan diff UI: surface lifecycle deltas from scan_history #38

Description

@kocaemre

Idea

`scan_history` + `ports.first_seen_scan_id` / `last_seen_scan_id` / `closed_at_scan_id` already track every rescan and which ports came / went / stayed (P1-G PR 1). Today the data is invisible — operators rescan and have no idea what changed unless they manually compare.

A first-class diff UI turns the existing schema into a feature.

Proposal

Engagement header badge

After a rescan, the header shows a chip like `+3 / -1 / changed: 2 since 12:04` referencing the latest scan_history row vs the prior one. Click → diff page.

`/engagements/[id]/diff?from=N&to=M` page

Three columns:

  • New ports (`first_seen_scan_id == M`) — green left border, "opened in this scan"
  • Closed ports (`closed_at_scan_id == M` AND were observed in N) — red left border, "closed since last scan"
  • Reaffirmed with changes (port survived but service / product / version / NSE script output diff) — yellow

Each row links to the port detail pane. Service / version diffs render as inline strikethrough → arrow → new value.

Compare picker

Dropdown on the diff page: `from = scan N`, `to = scan M`. Defaults: `to = latest`, `from = previous`. Listing already lives in `listScanHistory`.

Reopen detection

A port that was closed in scan N-1 and seen again in scan N is its own category — "reopened". Lifecycle columns already cover this (closed_at_scan_id cleared on reaffirm); the diff page just needs to surface it.

Heatmap chip

Each port tile gets a small overlay when it was new / reopened in the latest scan, fading after 24h or when the operator views it.

Out of scope

  • Rolling diff across 3+ scans (just pairwise to start)
  • Diffing AutoRecon artifacts — only nmap-derived port surface for v1

Acceptance

  • Engagement header shows the delta badge after a rescan
  • Diff page renders the three buckets correctly against fixture data
  • NSE script output diffs use a unified-diff style render (existing scripts pane has the chrome)
  • Lifecycle data already in DB is sufficient — no new migrations

Notes

Highest-leverage win for ops who actually rescan. Cost = pure UI + data joins — no schema change, no parser change.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions