<#1078 Add the vulnerability monitoring frontend> ...
# osctrl
g
#1078 Add the vulnerability monitoring frontend Pull request opened by javuto Add the vulnerability monitoring frontend Follow-up to #1076, which added the opt-in vulnerability monitoring backend. This PR gives it a UI: a findings page per environment, an advisory dialog, a Vulnerabilities tab on the node page, and a way to turn on inventory collection from the environment schedule. Problem The backend matches node software against OSV and the CISA KEV catalog, but its results were only reachable over REST. Operators had no way to see which nodes are exposed, drill into an advisory, check whether the advisory feeds are healthy, or add the inventory queries to an environment without hand-editing schedule JSON. What changed Everything is gated on
features.vulnerabilities
, which is set by
--vuln-enabled
on osctrl-api. With the flag off, nothing new renders and no vulnerability endpoint is called. • Environment → Vulnerabilities (
/_app/env/{env}/vulnerabilities
) • Summary cards: open findings, known exploited (KEV), affected nodes, not assessed. • Severity filter chips with counts, a "known exploited only" filter, and a state filter (open / resolved / all). • A paged findings table: advisory, package and ecosystem, installed version, fixed version or "No fix yet", severity, KEV and confidence badges, node, first seen. • Filters and page live in the URL, so Back returns to the same view and an environment switch starts on page 1. • Feeds card (super admins only): per-source last success and last error, plus Sync now, which queues a sync for the worker. • Advisory dialog: summary, details, CVSS, aliases, references, and the affected nodes in this environment. • Node page → Vulnerabilities tab: the node's open findings and how many of its packages couldn't be assessed. • Sidebar entry, shown only to admins of the current environment (the same check the API makes). • Configuration → Schedule → "Add vulnerability inventory": a picker for the
vuln-linux
,
vuln-darwin
and
vuln-windows
inventory profiles. It merges them into the schedule with the existing posture merge. • i18n: a new
vulns
namespace plus
nav.vulnerabilities
and
pageTitle.vulnerabilities
, in all 20 locales. An empty list never reads as "no vulnerabilities": • Before the first feed sync, and whenever a feed is stale, a banner says so. • A node that hasn't reported inventory says so. • A node whose inventory hasn't been matched yet says matching is pending. • Packages that can't be assessed are counted, not hidden. • When a list reaches a server-side cap, it says "Only the first N are shown". The caps are 500 findings per node and 1,000 nodes per advisory. Security and operational impact • Untrusted feed content. Advisory summaries, details, aliases and references come from external feeds. • They render as plain React text; there is no
dangerouslySetInnerHTML
. • Only
https:
references become links, opening with
target="_blank" rel="noopener noreferrer"
. •
javascript:
,
data:
and plain
http:
URLs stay text. • Tests cover a
<script>
summary and a
javascript:
reference. • Authorization is still enforced by the API. The UI only hides what the user can't use: • environment surfaces need environment admin, matching `CheckPermissionsContext(AdminLevel, env)`; • the feeds card and Sync now need super admin. • Request paths. Advisory IDs are URL-encoded (
RHSA-2026:76750
becomes
RHSA-2026%3A76750
). • No backend, schema or configuration changes. Deviations from the design spec • Advisory "drawer" → a dialog. The SPA has an accessible
ModalShell
and no drawer primitive. • No separate "Admin → Vulnerability feeds" page. The same status and action are a super-admin card on the environment page. • The node tab's label is hard-coded English, like the other tab labels on that page; the tab's content is translated. • No Playwright flow.
frontend/
has no Playwright config or specs to build on. Review notes • Translations: the 19 non-English translations are unreviewed drafts. They avoid ASCII apostrophes, which ICU treats as escapes. A native read is worth it, especially for ar, fa, he, hi, ja, ko and zh. • Node column: findings show an 8-character node ID prefix rather than the hostname, because the API doesn't return hostnames. • Known minor gaps, deferred: • a failed
/summary
request is swallowed; if the findings list is also empty, the page shows no banner and just "No findings match"; • while data isn't loaded, the cards still show 0 under the banner; • severity chip counts ignore the State and KEV filters; • a bookmarked page with the feature off shows the raw 404; • the feeds card shows only headers while loading, and a finished sync doesn't refresh the summary. Validation • `npm run check`:
tsc
clean. • `npm test`: 64 files, 486 tests pass. New coverage: • API client: URL building and encoding; • i18n catalog parity across all 20 locales; • link sanitizing and Go zero-time handling; • advisory dialog: inert feed text, https-only links, the 404 state, a node link closing the dialog, the 1,000-node cap notice; • environment page: • summary, not-loaded and stale banners; • a filter change resets to page 1; • an environment switch starts on page 1; • Back keeps the filters; • the API error shows instead of an empty table; • feeds card and Sync now for super admins only; • sidebar entry gating: feature flag and environment admin; • node tab: findings, no inventory, Go zero time, match pending, the 500-finding cap notice, the error state; the tab is hidden when the feature is off; • inventory picker, and merging a profile into the schedule. •
npm run build
succeeds. • Mutation-checked (the tests fail with the guard removed): the https-only link guard, the page-1 reset on filter change, and the environment-admin gate on the sidebar entry. • A fresh whole-branch review found four issues that would mislead operators. Each is fixed with a test that failed first: • an environment switch kept the old page number; • Back lost the filters; • the dialog's node link didn't close the dialog; • lists truncated silently at the server caps; • a node read "no findings" before its inventory was matched. • Not done: • a manual run against a live API and TLS with `--vuln-enabled`; • screenshots of the Vulnerabilities page, the advisory dialog, the node tab and the inventory picker, which are still to be attached. jmpsec/osctrl