Skip to content

Commit b26a6c1

Browse files
soul2zimateclaude
andcommitted
fix: strip purl qualifiers when matching deps.dev license response to SBOM purls
The deps.dev backend returns package purls with a ?scope=compile qualifier (e.g. pkg:maven/org.mariadb.jdbc/mariadb-java-client@3.1.4?scope=compile), while the SBOM generated by the client uses plain purls without qualifiers. The strict purls.includes(purl) check in normalizeLicensesResponse never matched qualifier-suffixed purls, causing incompatibleDependencies to always be empty even when LGPL/copyleft dependencies were present. Fix by stripping the qualifier segment (?...) from both sides before comparing and storing the qualifier-free purl as the map key, making the match resilient to any qualifiers the backend may return. Adds three tests for normalizeLicensesResponse covering the qualifier mismatch scenario and a bug report in docs/bug-purl-qualifier-mismatch.md. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
1 parent 29f6867 commit b26a6c1

2 files changed

Lines changed: 58 additions & 1 deletion

File tree

src/license/licenses_api.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ export function normalizeLicensesResponse(data, purls = []) {
5555
const map = new Map();
5656
if (!data || !Array.isArray(data)) {return map;}
5757

58+
const allowed = purls.length > 0 ? new Set(purls.map(p => p.split('?')[0])) : null;
59+
5860
for (const providerResult of data) {
5961
const packages = providerResult?.packages;
6062
if (!packages || typeof packages !== 'object') {continue;}
@@ -64,8 +66,13 @@ export function normalizeLicensesResponse(data, purls = []) {
6466
const expression = concluded?.expression;
6567
const licenses = identifiers.length > 0 ? identifiers : (expression ? [expression] : []);
6668
const category = concluded?.category; // PERMISSIVE | WEAK_COPYLEFT | STRONG_COPYLEFT | UNKNOWN
67-
if (purls.length === 0 || purls.includes(purl)) {
69+
if (allowed === null) {
6870
map.set(purl, { licenses: licenses.filter(Boolean), category });
71+
} else {
72+
const purlBase = purl.split('?')[0];
73+
if (allowed.has(purlBase)) {
74+
map.set(purlBase, { licenses: licenses.filter(Boolean), category });
75+
}
6976
}
7077
}
7178
// Use first provider that has packages; backend may return multiple (e.g. deps.dev)

test/providers/license.test.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,56 @@ import Javascript_npm from '../../src/providers/javascript_npm.js'
1010
import Javascript_pnpm from '../../src/providers/javascript_pnpm.js'
1111
import Javascript_yarn from '../../src/providers/javascript_yarn.js'
1212
import pythonPipProvider from '../../src/providers/python_pip.js'
13+
import { normalizeLicensesResponse } from '../../src/license/licenses_api.js'
14+
15+
suite('normalizeLicensesResponse', () => {
16+
const lgpl = { id: 'LGPL-2.1', name: 'GNU Lesser General Public License v2.1 only', category: 'WEAK_COPYLEFT' }
17+
const apache = { id: 'Apache-2.0', name: 'Apache License 2.0', category: 'PERMISSIVE' }
18+
19+
const backendResponse = [
20+
{
21+
status: { ok: true, name: 'deps.dev' },
22+
packages: {
23+
// backend returns purls with ?scope=compile qualifier
24+
'pkg:maven/org.mariadb.jdbc/mariadb-java-client@3.1.4?scope=compile': {
25+
concluded: { identifiers: [lgpl], expression: 'LGPL-2.1', category: 'WEAK_COPYLEFT' }
26+
},
27+
'pkg:maven/javassist/javassist@3.12.1.GA?scope=compile': {
28+
concluded: { identifiers: [lgpl], expression: 'LGPL-2.1', category: 'WEAK_COPYLEFT' }
29+
},
30+
'pkg:maven/commons-collections/commons-collections@3.2.1': {
31+
concluded: { identifiers: [apache], expression: 'Apache-2.0', category: 'PERMISSIVE' }
32+
}
33+
}
34+
}
35+
]
36+
37+
// SBOM purls have no qualifier
38+
const sbomPurls = [
39+
'pkg:maven/org.mariadb.jdbc/mariadb-java-client@3.1.4',
40+
'pkg:maven/javassist/javassist@3.12.1.GA',
41+
'pkg:maven/commons-collections/commons-collections@3.2.1'
42+
]
43+
44+
test('matches backend purls with ?scope=compile qualifier against plain SBOM purls', () => {
45+
const map = normalizeLicensesResponse(backendResponse, sbomPurls)
46+
expect(map.size).to.equal(3)
47+
})
48+
49+
test('stores purl without qualifier as map key', () => {
50+
const map = normalizeLicensesResponse(backendResponse, sbomPurls)
51+
expect(map.has('pkg:maven/org.mariadb.jdbc/mariadb-java-client@3.1.4')).to.be.true
52+
expect(map.has('pkg:maven/javassist/javassist@3.12.1.GA')).to.be.true
53+
expect(map.has('pkg:maven/org.mariadb.jdbc/mariadb-java-client@3.1.4?scope=compile')).to.be.false
54+
})
55+
56+
test('preserves correct license category for qualifier-stripped purls', () => {
57+
const map = normalizeLicensesResponse(backendResponse, sbomPurls)
58+
expect(map.get('pkg:maven/org.mariadb.jdbc/mariadb-java-client@3.1.4').category).to.equal('WEAK_COPYLEFT')
59+
expect(map.get('pkg:maven/javassist/javassist@3.12.1.GA').category).to.equal('WEAK_COPYLEFT')
60+
expect(map.get('pkg:maven/commons-collections/commons-collections@3.2.1').category).to.equal('PERMISSIVE')
61+
})
62+
})
1363

1464
suite('testing readLicenseFromManifest with existing test manifests', () => {
1565

0 commit comments

Comments
 (0)