Security page UX fixes:
- Stop the CVSS x EPSS scatter chart from painting a full-plot "white
rectangle" cursor on click (cursor disabled), and prevent click-drag
selection on charts.
- Differentiate the overview action links: "fixable" links (masthead primary
action, review-queue blocker, and the Fixable signal tile) now open the
Images tab pre-filtered to fixable findings; the Stale and Failed signal
tiles link to the History tab where those scans are listed; Secrets and
Misconfigs tiles link to their tabs. The Images tab accepts an initialFilter
and exposes a Fixable option in the severity dropdown.
- Fix the "Secrets / misconfigs" option wrapping and misaligning in the
severity dropdown (single-line option labels, wider trigger).
- Add Edit for CVE suppressions and misconfig acknowledgements (reason, scope
pattern, expiry), reusing the existing dialog and the existing PUT endpoints;
the CVE/rule identity stays fixed.
The Security overview's top exploit-risk list is built from a query capped at
2000 rows. The query had no ORDER BY, so when a node had more findings than the
cap the rows kept were arbitrary: the list could rank and display a subset that
omitted higher-risk findings, and the frontend discarded the truncated flag the
endpoint already returned, so nothing told the operator the list was partial.
- The query now orders by known-exploited, then EPSS, then CVSS before the cap,
so the rows that survive truncation are the highest-risk ones, matching the
client-side ranking the list applies.
- SecurityView keeps the truncated flag and threads it through to the list,
which now shows a short "more exist than can be listed here" note when the set
was capped.
Also fixes a presentation regression: the list colored every non-Critical
severity dot with the High color, so a Medium or Low known-exploited finding
(now surfaced alongside Critical/High) showed as High. The dot now maps to the
finding's actual severity.
Image scan sheet: make the finding table the single bounded scroll region
(SystemSheet noScroll + flex-fill) so it no longer clips at the bottom on
shorter viewports, and give the tables a phone min-width so they scroll
horizontally instead of cutting off the right-hand columns.
Scan history table: add the same horizontal scroll on phones.
Overview "Top exploit-risk findings": render as a paginated table with column
headers and top-right pagination, modeled on the dashboard stack-health table.
Key the rows by rank position so recurring CVE/scan pairs no longer collide and
duplicate rows when paging through.
Overview severity-by-exploitability chart: label the axes (EPSS exploitability
and CVSS severity) and stop the card from stretching to a taller neighbour,
removing the dead space beneath the chart.
* feat(security): rework Overview charts around posture and exploit intel
Replace the three severity-variation charts (severity donut, top exposed images,
findings by type) with prioritization views, keeping the risk trend for context:
- Action posture: bars of fixable / known-exploited / needs-review / accepted /
not-affected with a known-exploited headline, derived from the existing
overview facts (no new fetch).
- Top exploit-risk findings: ranked actionable Critical/High by KEV, then EPSS,
then CVSS, each row opening its scan.
- Severity x exploitability: a CVSS-by-EPSS quadrant that separates
scary-but-not-exploitable from act-first.
The two intel panels are fed by a new bounded GET /security/overview/exploit-intel
(latest-scan Critical/High, suppression-filtered, KEV/EPSS joined at read time)
and degrade to clear empty states until exploit intel is fetched and images are
rescanned.
* fix(security): drop the redundant SECURITY kicker from the desktop masthead
The desktop nav strip already names the page, so the masthead's "SECURITY" label
above the posture word was redundant. Make PageMasthead's kicker optional (pages
that pass one render unchanged) and omit it on the Security masthead. The mobile
masthead keeps its kicker, since on the phone layout it is the page identity and
there is no nav strip.
* docs(security): describe the prioritization-led Overview charts
Update the Security overview docs for the reworked chart set (risk trend, action
posture, top exploit-risk, and the severity-by-exploitability quadrant) and note
the exploit-risk charts populate once exploit intelligence is enabled.
* fix(security): move the scanner-detections note into a masthead info icon
Replace the standing "scanner detections show vulnerable components..." caption
below the masthead (desktop and mobile) with an info affordance next to the
scanned-images count in the masthead subtitle. Declutters the overview while
keeping the disclaimer one hover away.
* fix(security): apply "assume it's automatable" to exploit-risk ranking
Absence of exploitability evidence must not be treated as low risk. Rank the top
exploit-risk list by tier (known-exploited > known-high EPSS > unknown EPSS >
known-low EPSS) so an unrated finding outranks one with evidence of low
likelihood; label unrated findings "EPSS n/a"; and reword the quadrant footnote
so excluded findings read as unrated rather than lower risk.
* feat(appearance): add Calm/Signature visual style, readability mode, and chart palette
Turn the "too intense / italic headers hurt / the security graph fights my
eyes" feedback into a token-driven Visual style with Calm as the new default
and Signature one click back to the prior look.
- Heading family routes through a `.font-heading` utility driven by
`--font-heading`/`--heading-style`: operational headings render upright in the
interface face under Calm and italic Instrument Serif under Signature. Base
rule sets family + style only, so each call site keeps its own weight/tracking
and Signature stays a true no-op; the Calm lift is a `[data-headings="clean"]`
descendant rule. Brand lockup, empty-state heroes, and onboarding stay serif.
- Severity charts resolve through `--sev-*` tokens with Muted, Heat, and
Signature palettes; FindingsByType routes its series through the severity ramp
plus a neutral so no brand-cyan sits next to rose. The risk trend flattens its
gradient under Muted/Heat/reduced and keeps the gradient under Signature.
- Appearance settings gain Visual style cards, a Security visualization palette,
a Readability master toggle, a Motion & effects group, and a "Reset to default"
button (restores the Calm axes, disabled while readability is on). Contrast
moves under Readability and Ambient glow under Motion & effects. A card is
selected only while the stored sub-axes match its preset, so a custom
combination de-selects both.
- The topbar Theme quick-switch swaps the interface/data font pickers for a
Visual style switch and a Readability toggle (text size kept); its footer
Settings link jumps straight to Appearance.
- Readability is a sticky master that forces the calm resolution and a contrast
lift at apply time without mutating the stored sub-axes.
- New users default to Calm; any pre-existing persisted appearance state keeps
the Signature look. The pre-paint script mirrors the store.
- SegmentedControl gains a `disabled` prop and a nullable value (no active
segment for a custom combination, with a roving-tabindex keyboard anchor).
Adds unit/component coverage for the store, migration, chart shape logic, the
disabled control, the reset/de-selection, and the quick-switch.
* fix(appearance): migrate Blueprint serif headings and surface readability locks
- Migrate the two operational Blueprint headings (catalog tile name, drift-policy
option title) from font-serif italic to the .font-heading utility; the first
pass only covered font-display, so Calm still left these italic. font-serif and
font-display both resolve to the same display face, so this is the same fix.
- Lock the Visual style cards under Readability (parity with the topbar switch and
the on-screen guidance to turn Readability off to choose a style by hand).
- Lock the Border brightness slider under Readability and show its forced +0.03
readout, since Readability overrides the stored value; dragging it previously
appeared to do nothing.
- Correct the Appearance docs sentence for the topbar quick switch (it listed
fonts; the quick switch now carries visual style, readability, and text size).
* feat: chart-led Security overview with sortable Images and History tables
Refine the Security page around the existing design system and add the
data the dashboard needs.
- Overview leads with four charts (30-day risk trend, severity donut, top
exposed images, findings by type); the signal-rail counts become a
secondary summary, and the scanner and deploy-enforcement posture follow.
- Images becomes a recessed table with search, a severity filter, sortable
columns, a last-scan column, and inline scan actions; the findings cell is
clickable into the scan sheet, and the per-row cursor tooltip is dropped
where the columns already carry that information.
- Policies puts deploy-enforcement first, collapses the policy packs into an
accordion, and uses the standard primary button for Add policy.
- Suppressions and acknowledgements move their titles and Add buttons outside
the cards, matching the Fleet tab layout.
- History switches from the detail sheet to an inline table (search, sortable
columns, two-scan compare, pagination); the now-unreachable scan-history
overlay is removed.
- Add GET /api/security/overview/trend, a node-scoped daily critical/high
rollup backing the risk-trend chart.
- Extract the shared image-scan hook and the severity classifier, and harden
the overview data fetch so a malformed non-critical response can never read
as a clean security state.
* fix: treat malformed Security responses as errors, not empty or clean states
Address an independent review of the data-fetch paths so a 200 with an
unexpected shape can never read as a benign "no findings" view.
- SecurityView: validate that the image-summaries body is a scan-summary map; an
unexpected shape now sets the error state instead of an empty map. Isolate the
trend fetch in its own self-catching promise so a transport failure on the
non-critical chart can no longer poison the overview or summaries error state.
- useImageScan: only a "completed" poll counts as success (a malformed or unknown
status now throws), and a failed post-scan summaries refresh is logged instead
of silently dropped.
- HistoryTab: a 200 whose body lacks an items array is treated as an error, not
an empty "no completed scans" list.