Skip to content

Commit ebfc47b

Browse files
committed
fix: validate CVSS Scope metric to prevent silent mis-scoring
cvssScoring.ts read `v.S` directly into the `s === 'C' ? ... : ...` branches but never validated it. A vector missing `S` or carrying an invalid value like `S:X` would silently take the Scope:Unchanged branch in every formula and return a wrong numeric score instead of null. The 10 reference-vector tests didn't catch it because every test vector had a valid S:U or S:C. This is the exact failure mode ADR-0005 named as the headline risk of choosing inline scoring over the cvss npm package — wrong scores feed advisories.is_critical and packages.has_critical_vulnerability, i.e. the entire security overlay. Fix: validate `s` against {U, C} up front and return null otherwise. Added two regression tests covering the missing-S and invalid-S paths. Caught by Cursor's bot review on cbaf41d. Signed-off-by: Joan Reyero <joan@reyero.io>
1 parent 20398ea commit ebfc47b

2 files changed

Lines changed: 17 additions & 0 deletions

File tree

services/apps/packages_worker/src/osv/__tests__/cvssScoring.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,16 @@ describe('computeV3Score', () => {
4242
expect(computeV3Score('CVSS:3.1/AV:Z/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H')).toBeNull() // bad enum
4343
})
4444

45+
// Regression guards for the unvalidated-Scope bug: missing or invalid S used
46+
// to silently produce a Scope:Unchanged score instead of returning null.
47+
it('returns null when the Scope metric is missing', () => {
48+
expect(computeV3Score('CVSS:3.1/AV:N/AC:L/PR:N/UI:N/C:H/I:H/A:H')).toBeNull()
49+
})
50+
51+
it('returns null when the Scope metric is invalid', () => {
52+
expect(computeV3Score('CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:X/C:H/I:H/A:H')).toBeNull()
53+
})
54+
4555
it('handles scope-changed PR remapping', () => {
4656
// PR:L scores higher under Scope:Changed than under Scope:Unchanged.
4757
const unchanged = computeV3Score('CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H')

services/apps/packages_worker/src/osv/cvssScoring.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const PR_U: Record<string, number> = { N: 0.85, L: 0.62, H: 0.27 }
1313
const PR_C: Record<string, number> = { N: 0.85, L: 0.68, H: 0.5 }
1414
const UI: Record<string, number> = { N: 0.85, R: 0.62 }
1515
const CIA: Record<string, number> = { N: 0, L: 0.22, H: 0.56 }
16+
const S_VALUES = new Set(['U', 'C'])
1617

1718
function parseVector(vector: string): Record<string, string> | null {
1819
const parts = vector.split('/').filter(Boolean)
@@ -41,7 +42,13 @@ export function computeV3Score(vector: string): number | null {
4142
const av = AV[v.AV]
4243
const ac = AC[v.AC]
4344
const ui = UI[v.UI]
45+
// Scope is read directly because the metric is qualitative, not numeric, so
46+
// it does not slot into the undefined-check below. Validate it explicitly
47+
// here — an invalid or missing S would otherwise silently fall through to
48+
// the Scope:Unchanged formula and produce a wrong numeric score instead of
49+
// null, which is the headline risk flagged in ADR-0005.
4450
const s = v.S
51+
if (!S_VALUES.has(s)) return null
4552
const pr = s === 'C' ? PR_C[v.PR] : PR_U[v.PR]
4653
const c = CIA[v.C]
4754
const i = CIA[v.I]

0 commit comments

Comments
 (0)