Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/.env.dist.local
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ OSSPCKGS_GCP_CREDENTIALS_B64=e30=
# maven/all.zip 404s). The allowlist check and DB storage normalize to lowercase
# internally per ADR-0001 §OSV "Ecosystem normalization", so downstream stays lowercase.
OSV_BULK_BASE_URL=https://osv-vulnerabilities.storage.googleapis.com
OSV_ECOSYSTEMS=npm,Maven,cargo
OSV_ECOSYSTEMS=npm,Maven,cargo,NuGet
OSV_TMP_DIR=/tmp/osv
OSV_BATCH_SIZE=500
OSV_DERIVE_BATCH_SIZE=1000
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,34 @@ describe('compareVersion — maven (ComparableVersion-style)', () => {
})
})

describe('compareVersion — nuget (semver)', () => {
it.each([
['1.0.0', '2.0.0', -1],
['2.0.0', '1.0.0', 1],
['1.0.0', '1.0.0', 0],
['1.10.0', '1.9.0', 1], // numeric, not lex
['1.0.0-alpha', '1.0.0', -1], // prerelease < release
['1.0.0-beta', '1.0.0-rc', -1],
// Real-world CVE boundary: Newtonsoft.Json < 13.0.1 deserialization vuln
['13.0.0', '13.0.1', -1],
['13.0.1', '13.0.1', 0],
['13.0.2', '13.0.1', 1],
])('compareVersion("nuget", %s, %s) sign = %s', (a, b, expected) => {
expect(sign(compareVersion('nuget', a, b))).toBe(expected)
})

it('returns null for unparseable nuget versions', () => {
expect(compareVersion('nuget', 'not-a-version', '1.0.0')).toBeNull()
})

it('rejects titlecase "NuGet" — production storage is always lowercase', () => {
// OSV records arrive with ecosystem="NuGet"; parseOsvRecord lowercases to
// "nuget" before writing to packages-db. The comparator is keyed on the
// same lowercase form. A titlecase call means the caller skipped normalization.
expect(compareVersion('NuGet', '1.0.0', '2.0.0')).toBeNull()
})
})

describe('compareVersion — unsupported ecosystems', () => {
it('returns null for ecosystems we have no comparator for', () => {
expect(compareVersion('PyPI', '1.0.0', '2.0.0')).toBeNull()
Expand Down
2 changes: 1 addition & 1 deletion services/apps/packages_worker/src/osv/schedule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const SCHEDULE_ID = 'osv-advisories-sync'
// validate the env input against this list and refuse to register the
// schedule on a mismatch — better a loud startup error than a silent miss.
// Add new entries here when v1 expands beyond npm + Maven.
const VALID_ECOSYSTEMS = ['npm', 'Maven', 'cargo'] as const
const VALID_ECOSYSTEMS = ['npm', 'Maven', 'cargo', 'NuGet'] as const
Comment on lines 13 to +14

function getEcosystems(): string[] {
const raw = process.env.OSV_ECOSYSTEMS
Expand Down
4 changes: 3 additions & 1 deletion services/apps/packages_worker/src/osv/versionCompare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,13 @@ function compareMaven(a: string, b: string): number | null {
return 0
}

const SEMVER_ECOSYSTEMS = new Set(['npm', 'cargo', 'nuget'])
Comment thread
mbani01 marked this conversation as resolved.

// Ecosystem names are stored lowercase in packages-db per ADR-0001 §OSV
// "Ecosystem normalization" — 'npm', 'maven', 'cargo'. Callers (deriveCriticalFlag)
// pull the value straight from the DB so the literals here must match.
Comment on lines +129 to 133
export function compareVersion(ecosystem: string, a: string, b: string): number | null {
if (ecosystem === 'npm' || ecosystem === 'cargo') return compareSemver(a, b)
if (SEMVER_ECOSYSTEMS.has(ecosystem)) return compareSemver(a, b)
if (ecosystem === 'maven') return compareMaven(a, b)
return null
}
Loading