Skip to content

Commit ef853f4

Browse files
committed
Merge pod/v1f-cve-severity into pod/v1c-ci-fixtures
2 parents 7ba41aa + c60255e commit ef853f4

3 files changed

Lines changed: 91 additions & 6 deletions

File tree

slices/v1-f/verify.sh

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
cd "$(dirname "$0")/../.."
4+
npm test -- tests/scanners/dependencies.test.ts
5+
node -e "
6+
const { parseDependencies, lookupCves, detectPackageManagers } = require('./dist/scanners/dependencies');
7+
const path = require('path');
8+
const root = path.join(__dirname, 'test-data/vulnerable-deps');
9+
const files = require('fs').readdirSync(root).map(f => path.join(root, f));
10+
const mgrs = detectPackageManagers([path.join(root, 'package.json')], root);
11+
lookupCves(parseDependencies(mgrs)).then(r => {
12+
const bad = r.filter(d => d.vulnerabilities.length > 0 && d.maxSeverity === 'None');
13+
if (bad.length) process.exit(1);
14+
console.log('ok');
15+
});
16+
"

src/scanners/dependencies.ts

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,77 @@ function scoreToSeverity(score: number): FindingSeverity {
138138
return 'None'; // Should not happen if thresholds cover 0
139139
}
140140

141+
const SEVERITY_RANK: Record<FindingSeverity, number> = {
142+
None: 0,
143+
Info: 1,
144+
Low: 2,
145+
Medium: 3,
146+
High: 4,
147+
Critical: 5,
148+
};
149+
150+
function higherSeverity(a: FindingSeverity, b: FindingSeverity): FindingSeverity {
151+
return SEVERITY_RANK[a] >= SEVERITY_RANK[b] ? a : b;
152+
}
153+
154+
/**
155+
* Maps OSV/GitHub advisory severity labels (e.g. database_specific.severity) to FindingSeverity.
156+
*/
157+
function mapOsvSeverityLabel(label: string): FindingSeverity | null {
158+
switch (label.toUpperCase()) {
159+
case 'CRITICAL':
160+
return 'Critical';
161+
case 'HIGH':
162+
return 'High';
163+
case 'MODERATE':
164+
case 'MEDIUM':
165+
return 'Medium';
166+
case 'LOW':
167+
return 'Low';
168+
default:
169+
return null;
170+
}
171+
}
172+
173+
/**
174+
* Reads ecosystem severity from an OSV vuln when CVSS v3 is absent.
175+
*/
176+
function parseOsvEcosystemSeverity(vuln: OSVulnerability): FindingSeverity | null {
177+
const raw = vuln.database_specific?.severity;
178+
if (typeof raw !== 'string') {
179+
return null;
180+
}
181+
return mapOsvSeverityLabel(raw);
182+
}
183+
184+
/**
185+
* Computes max severity across vulns: CVSS v3 first, then OSV ecosystem severity,
186+
* else Medium when vulns exist but no score is available (batch API often omits CVSS).
187+
*/
188+
function computeMaxSeverityFromVulns(vulns: OSVulnerability[]): FindingSeverity {
189+
let maxCvss = 0;
190+
let maxOsvSeverity: FindingSeverity | null = null;
191+
192+
for (const vuln of vulns) {
193+
maxCvss = Math.max(maxCvss, getHighestCvssScore(vuln.severity));
194+
const osvSeverity = parseOsvEcosystemSeverity(vuln);
195+
if (osvSeverity) {
196+
maxOsvSeverity = maxOsvSeverity
197+
? higherSeverity(maxOsvSeverity, osvSeverity)
198+
: osvSeverity;
199+
}
200+
}
201+
202+
if (maxCvss > 0) {
203+
return scoreToSeverity(maxCvss);
204+
}
205+
if (maxOsvSeverity) {
206+
return maxOsvSeverity;
207+
}
208+
// OSV batch responses may omit CVSS; still surface known vulns at Medium minimum.
209+
return 'Medium';
210+
}
211+
141212
// Define the return type for the detection function
142213
type DetectedFilesMap = { [key in PackageManager]?: { manifest?: string, lock?: string } };
143214

@@ -339,12 +410,7 @@ export async function lookupCves(dependencies: DependencyInfo[]): Promise<Depend
339410
if (targetFinding) {
340411
if (result && result.vulns && result.vulns.length > 0) {
341412
targetFinding.vulnerabilities = result.vulns;
342-
// Calculate max severity for this dependency
343-
let maxCvss = 0;
344-
for (const vuln of result.vulns) {
345-
maxCvss = Math.max(maxCvss, getHighestCvssScore(vuln.severity));
346-
}
347-
targetFinding.maxSeverity = scoreToSeverity(maxCvss);
413+
targetFinding.maxSeverity = computeMaxSeverityFromVulns(result.vulns);
348414
} else {
349415
targetFinding.maxSeverity = 'None'; // Explicitly set to None if no vulns found
350416
}

tests/scanners/dependencies.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,8 @@ describe('dependencies scanner', () => {
1717
);
1818

1919
expect(vulnerable.length).toBeGreaterThan(0);
20+
for (const finding of vulnerable) {
21+
expect(finding.maxSeverity).not.toBe('None');
22+
}
2023
});
2124
});

0 commit comments

Comments
 (0)