Skip to content

Commit 41d542e

Browse files
authored
fix(license): treat UNKNOWN-category dependencies as incompatible (#548)
Split the UNKNOWN category check in getCompatibility() so that dependencies with UNKNOWN license category are flagged as incompatible when the project has a known category. Add a distinct reason message for unrecognized licenses recommending manual review. Project-UNKNOWN still returns 'unknown' to avoid false positives. Implements TC-4706 Assisted-by: Claude Code
1 parent 5cc5120 commit 41d542e

3 files changed

Lines changed: 43 additions & 2 deletions

File tree

src/license/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,14 @@ export async function runLicenseCheck(sbomContent, manifestPath, url, opts = {},
9595

9696
const status = getCompatibility(projectCategory, entry.category);
9797
if (status === 'incompatible') {
98+
const reason = entry.category?.toUpperCase() === 'UNKNOWN'
99+
? 'License not recognized as a standard SPDX identifier. Manual review recommended to verify compatibility.'
100+
: 'Dependency license(s) are incompatible with the project license.';
98101
incompatibleDependencies.push({
99102
purl,
100103
licenses: entry.licenses,
101104
category: entry.category,
102-
reason: 'Dependency license(s) are incompatible with the project license.'
105+
reason
103106
});
104107
}
105108
}

src/license/license_utils.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,14 @@ export function getCompatibility(projectCategory, dependencyCategory) {
102102
const proj = projectCategory.toUpperCase();
103103
const dep = dependencyCategory.toUpperCase();
104104

105-
if (proj === 'UNKNOWN' || dep === 'UNKNOWN') {
105+
if (proj === 'UNKNOWN') {
106106
return 'unknown';
107107
}
108108

109+
if (dep === 'UNKNOWN') {
110+
return 'incompatible';
111+
}
112+
109113
const restrictiveness = {
110114
'PERMISSIVE': 1,
111115
'WEAK_COPYLEFT': 2,

test/providers/license.test.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ 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'
1313
import rustCargoProvider from '../../src/providers/rust_cargo.js'
14+
import { getCompatibility } from '../../src/license/license_utils.js'
1415
import { normalizeLicensesResponse } from '../../src/license/licenses_api.js'
1516

1617
suite('normalizeLicensesResponse', () => {
@@ -62,6 +63,39 @@ suite('normalizeLicensesResponse', () => {
6263
})
6364
})
6465

66+
suite('getCompatibility with UNKNOWN category', () => {
67+
/** @type {Array<{proj: string, dep: string, expected: string}>} */
68+
const cases = [
69+
{ proj: 'PERMISSIVE', dep: 'UNKNOWN', expected: 'incompatible' },
70+
{ proj: 'WEAK_COPYLEFT', dep: 'UNKNOWN', expected: 'incompatible' },
71+
{ proj: 'STRONG_COPYLEFT', dep: 'UNKNOWN', expected: 'incompatible' },
72+
{ proj: 'UNKNOWN', dep: 'PERMISSIVE', expected: 'unknown' },
73+
{ proj: 'UNKNOWN', dep: 'UNKNOWN', expected: 'unknown' },
74+
];
75+
76+
cases.forEach(({ proj, dep, expected }) => {
77+
/// Verifies getCompatibility returns the expected result for the given project/dependency category pair.
78+
test(`getCompatibility('${proj}', '${dep}') returns '${expected}'`, () => {
79+
expect(getCompatibility(proj, dep)).to.equal(expected);
80+
});
81+
});
82+
83+
/// Verifies that a null project category with UNKNOWN dependency returns 'unknown'.
84+
test("getCompatibility(null, 'UNKNOWN') returns 'unknown'", () => {
85+
expect(getCompatibility(null, 'UNKNOWN')).to.equal('unknown');
86+
});
87+
88+
/// Verifies that existing known-category incompatibility checks still work correctly.
89+
test("existing incompatibility check: STRONG_COPYLEFT dep vs PERMISSIVE proj returns 'incompatible'", () => {
90+
expect(getCompatibility('PERMISSIVE', 'STRONG_COPYLEFT')).to.equal('incompatible');
91+
});
92+
93+
/// Verifies that compatible known-category checks are unaffected.
94+
test("existing compatibility check: PERMISSIVE dep vs STRONG_COPYLEFT proj returns 'compatible'", () => {
95+
expect(getCompatibility('STRONG_COPYLEFT', 'PERMISSIVE')).to.equal('compatible');
96+
});
97+
});
98+
6599
suite('testing readLicenseFromManifest with existing test manifests', () => {
66100

67101
suite('Java Maven provider', () => {

0 commit comments

Comments
 (0)