Skip to content

Commit 428e5a5

Browse files
authored
feat: support cargo in osv worker [CM-1269] (#4241)
Signed-off-by: Mouad BANI <mouad-mb@outlook.com>
1 parent 7fe5c53 commit 428e5a5

6 files changed

Lines changed: 22 additions & 11 deletions

File tree

backend/.env.dist.local

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ OSSPCKGS_GCP_CREDENTIALS_B64=e30=
195195
# maven/all.zip 404s). The allowlist check and DB storage normalize to lowercase
196196
# internally per ADR-0001 §OSV "Ecosystem normalization", so downstream stays lowercase.
197197
OSV_BULK_BASE_URL=https://osv-vulnerabilities.storage.googleapis.com
198-
OSV_ECOSYSTEMS=npm,Maven
198+
OSV_ECOSYSTEMS=npm,Maven,cargo
199199
OSV_TMP_DIR=/tmp/osv
200200
OSV_BATCH_SIZE=500
201201
OSV_DERIVE_BATCH_SIZE=1000

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

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { compareVersion } from '../versionCompare'
66
// the contract is -1/0/1, but we only care about the sign.
77
const sign = (n: number | null) => (n === null ? null : Math.sign(n))
88

9-
describe('compareVersion — npm (semver)', () => {
9+
describe('compareVersion — npm/cargo (semver)', () => {
1010
it.each([
1111
['1.2.3', '1.2.4', -1],
1212
['1.2.4', '1.2.3', 1],
@@ -20,11 +20,21 @@ describe('compareVersion — npm (semver)', () => {
2020
expect(sign(compareVersion('npm', a, b))).toBe(expected)
2121
})
2222

23-
it('returns null for unparseable npm versions', () => {
23+
it.each([
24+
['0.1.0', '0.2.0', -1],
25+
['1.0.0', '1.0.0', 0],
26+
['2.0.0', '1.9.9', 1],
27+
['0.3.0-alpha.1', '0.3.0', -1],
28+
])('compareVersion("cargo", %s, %s) sign = %s', (a, b, expected) => {
29+
expect(sign(compareVersion('cargo', a, b))).toBe(expected)
30+
})
31+
32+
it('returns null for unparseable semver versions', () => {
2433
expect(compareVersion('npm', 'not-a-version-at-all', '1.0.0')).toBeNull()
34+
expect(compareVersion('cargo', 'not-a-version-at-all', '1.0.0')).toBeNull()
2535
})
2636

27-
it('returns null for short / lossy npm versions instead of coercing', () => {
37+
it('returns null for short / lossy versions instead of coercing', () => {
2838
// Under-flag over mis-flag: semver.coerce would map "1.2" → "1.2.0" and
2939
// "1.2-junk-3" → "1.2.3", which can flip has_critical_vulnerability on
3040
// a malformed introduced/fixed boundary. We prefer null (no match).
@@ -76,7 +86,6 @@ describe('compareVersion — maven (ComparableVersion-style)', () => {
7686
describe('compareVersion — unsupported ecosystems', () => {
7787
it('returns null for ecosystems we have no comparator for', () => {
7888
expect(compareVersion('PyPI', '1.0.0', '2.0.0')).toBeNull()
79-
expect(compareVersion('crates.io', '0.1', '0.2')).toBeNull()
8089
})
8190

8291
it('rejects titlecase "Maven" — production storage is always lowercase', () => {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ export async function* fetchEcosystemZip(
7070
await mkdir(ecoDir, { recursive: true })
7171
const zipPath = path.join(ecoDir, 'all.zip')
7272

73-
const url = `${baseUrl.replace(/\/$/, '')}/${ecosystem}/all.zip`
73+
const bucketEcosystem = ecosystem === 'cargo' ? 'crates.io' : ecosystem
74+
const url = `${baseUrl.replace(/\/$/, '')}/${bucketEcosystem}/all.zip`
7475

7576
try {
7677
await downloadZip(url, zipPath)

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ export function parseOsvRecord(
111111
for (const affected of record.affected ?? []) {
112112
const pkg = affected.package
113113
if (!pkg) continue
114-
const ecosystem = pkg.ecosystem.toLowerCase()
114+
const rawEcosystem = pkg.ecosystem.toLowerCase()
115+
const ecosystem = rawEcosystem === 'crates.io' ? 'cargo' : rawEcosystem
115116
if (!allowedEcosystems.has(ecosystem)) continue
116117

117118
const ranges = flattenRanges(affected)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const SCHEDULE_ID = 'osv-advisories-sync'
1111
// validate the env input against this list and refuse to register the
1212
// schedule on a mismatch — better a loud startup error than a silent miss.
1313
// Add new entries here when v1 expands beyond npm + Maven.
14-
const VALID_ECOSYSTEMS = ['npm', 'Maven'] as const
14+
const VALID_ECOSYSTEMS = ['npm', 'Maven', 'cargo'] as const
1515

1616
function getEcosystems(): string[] {
1717
const raw = process.env.OSV_ECOSYSTEMS

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ function parseLoose(v: string): semver.SemVer | null {
1414
return semver.parse(v, { loose: true })
1515
}
1616

17-
function compareNpm(a: string, b: string): number | null {
17+
function compareSemver(a: string, b: string): number | null {
1818
const pa = parseLoose(a)
1919
const pb = parseLoose(b)
2020
if (!pa || !pb) return null
@@ -127,10 +127,10 @@ function compareMaven(a: string, b: string): number | null {
127127
}
128128

129129
// Ecosystem names are stored lowercase in packages-db per ADR-0001 §OSV
130-
// "Ecosystem normalization" — 'npm' and 'maven'. Callers (deriveCriticalFlag)
130+
// "Ecosystem normalization" — 'npm', 'maven', 'cargo'. Callers (deriveCriticalFlag)
131131
// pull the value straight from the DB so the literals here must match.
132132
export function compareVersion(ecosystem: string, a: string, b: string): number | null {
133-
if (ecosystem === 'npm') return compareNpm(a, b)
133+
if (ecosystem === 'npm' || ecosystem === 'cargo') return compareSemver(a, b)
134134
if (ecosystem === 'maven') return compareMaven(a, b)
135135
return null
136136
}

0 commit comments

Comments
 (0)