GitHub
10/10/2026, 8:02 AMfeatures.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/osctrlGitHub
10/10/2026, 8:08 AM