refactor(remediation): give each CVE its own severity and advisories - #635
Conversation
Reviewer's GuideThe PR changes remediation data from lossy dependency-level CVE, severity, and advisory fields to a per-CVE vulnerabilities array, then derives aggregate severity only where needed and renders each CVE with its own severity and advisories. Documentation and tests are updated to cover extraction, merging, reporting, and recommendation-only behavior. Flow diagram for per-CVE remediation extraction and reportingflowchart LR
A["Provider issue data"] --> B["extractRemediations"]
B --> C["Remediation with vulnerabilities[]"]
C --> D["Each CVE keeps severity and advisories"]
C --> E["maxSeverity(vulnerabilities)"]
D --> F["Per-dependency report: one row per CVE"]
E --> G["Bundled and dry-run views: dependency severity"]
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #635 +/- ##
==========================================
+ Coverage 92.02% 92.10% +0.07%
==========================================
Files 45 45
Lines 10602 10668 +66
Branches 1921 1930 +9
==========================================
+ Hits 9757 9826 +69
+ Misses 845 842 -3
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
b92a0da to
8bc7dc5
Compare
ruromero
left a comment
There was a problem hiding this comment.
Two test fixture bugs confirmed by running the test suite and verifying against the backend API shape. The production code is correct — the changes to extractAdvisories and getFixedInPurl target the actual backend format.
Bug 1 — fixedIn must be an array (3 tests currently failing)
The backend Remediation.fixedIn has always been List<String>. The PR correctly removed the old typeof fixedIn === 'string' guard, but 3 of the new fixtures in per-CVE vulnerabilities still pass a string PURL. Running npx mocha test/remediation.test.js confirms: 3 failing, 32 passing — all 3 fail with TypeError: fixedInVersions.filter is not a function.
Fix: wrap each string in an array.
Bug 2 — Advisory fixture format wrong (2 tests fail after Bug 1 is fixed)
The backend produces advisories under issue.remediation.advisories[].advisory.{id,url} — exactly the shape the new extractAdvisories reads. issue.advisories at the issue level never existed in this backend. Two of the new tests set advisories at issue level; extractAdvisories silently drops them, causing vuln.advisories to be [].
Fix: move advisory data to issue.remediation.advisories = [{ advisory: { id, url } }].
Minor gap — maxSeverity not re-exported from the public entry point
maxSeverity is exported from src/remediation.js but not from src/index.js. External callers who previously read rem.severity (now removed from the Remediation type) have no accessible replacement without reaching into the internal module path.
Suggest adding it to src/index.js line 27:
export { extractRemediations, maxSeverity } from "./remediation.js"extractRemediations flattened every CVE on a dependency into one severity, one CVE list, and one merged advisory list. That is lossy: a dependency with a MEDIUM and a CRITICAL CVE reported both as CRITICAL, and advisories could no longer be traced back to the CVE they belong to. The report generator consumed exactly that flattened data, so its per-CVE table showed every row at the dependency's max severity with a shared advisory list — the extractor had the correct per-CVE answer at parse time and was discarding it before the report could use it. Model vulnerabilities as a per-CVE array instead, each entry keeping the severity and advisories it was reported with. The report now renders one row per CVE from its own data. A dependency-level severity is derived on demand via maxSeverity (used by the bundled and dry-run views) rather than stored, so it cannot drift out of sync with the underlying CVEs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
8bc7dc5 to
f2d6e3f
Compare
Description
extractRemediations flattened every CVE on a dependency into one severity, one CVE list, and one merged advisory list. That is lossy: a dependency with a MEDIUM and a CRITICAL CVE reported both as CRITICAL, and advisories could no longer be traced back to the CVE they belong to. The report generator consumed exactly that flattened data, so its per-CVE table showed every row at the dependency's max severity with a shared advisory list — the extractor had the correct per-CVE answer at parse time and was discarding it before the report could use it.
Model vulnerabilities as a per-CVE array instead, each entry keeping the severity and advisories it was reported with. The report now renders one row per CVE from its own data. A dependency-level severity is derived on demand via maxSeverity (used by the bundled and dry-run views) rather than stored, so it cannot drift out of sync with the underlying CVEs.
Before:

After:

Part of TC-5615
Checklist
Summary by Sourcery
Preserve vulnerability details per CVE and derive dependency-level severity from the underlying vulnerabilities.
New Features:
Bug Fixes:
Enhancements:
Tests:
Chores: