Skip to content

Commit d29c779

Browse files
ruromeroclaude
andcommitted
chore: apply lint fixes and simplify docs
Signed-off-by: Ruben Romero Montes <rromerom@redhat.com> Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 4ef8df6 commit d29c779

14 files changed

Lines changed: 172 additions & 134 deletions
Lines changed: 125 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,111 +1,151 @@
1-
# License resolution and compliance
1+
# License Resolution and Compliance
22

3-
This document describes how the client resolves the **project license** (from manifest vs repository file), how it uses the **backend licenses-by-purl endpoint** to get dependency licenses, and how **component analysis** can report when the project license is missing, inconsistent, or incompatible with dependency licenses.
3+
This document describes the license analysis features that help you understand your project’s license and check compatibility with your dependencies.
44

5-
## Goals
5+
## Overview
66

7-
1. **Resolve project license** from:
8-
- The manifest (e.g. `package.json` `license`, `pom.xml` `<licenses>`) when present.
9-
- A `LICENSE`, `LICENSE.md`, or `LICENSE.txt` file in the same directory as the manifest.
10-
2. **Report to the user** when the license declared in the manifest differs from the license text file.
11-
3. **Component analysis**: when the user runs component analysis (e.g. on opening a manifest), optionally check dependency licenses (via backend) and report if any are **incompatible** with the project license.
7+
License analysis is **enabled by default** and provides:
128

13-
## Resolving the project license
9+
1. **Project license detection** from your manifest file (e.g., `package.json`, `pom.xml`) and LICENSE files
10+
2. **Dependency license information** from the Trustify DA backend
11+
3. **Compatibility checking** to identify potential license conflicts
12+
4. **Mismatch detection** when your manifest and LICENSE file declare different licenses
1413

15-
### From the manifest
14+
## How It Works
1615

17-
| Ecosystem | Manifest | Where license is read |
18-
|----------------|-----------------|------------------------|
19-
| JavaScript | package.json | `license` (string) or `licenses` (array); can be SPDX id or "SEE LICENSE IN FILE" |
20-
| Java (Maven) | pom.xml | Effective POM: `<licenses><license><name>` / `<url>`; map common names to SPDX where possible |
21-
| Java (Gradle) | build.gradle(*) | No standard; some projects set extra/license in properties; optional best-effort |
22-
| Go | go.mod | No standard license field |
23-
| Python | requirements.txt| No license in manifest |
16+
### Project License Detection
2417

25-
(*) Gradle: license is often in a separate file or NOTICE; we do not parse build logic.
18+
The client looks for your project’s license in two places:
2619

27-
### From the repository (LICENSE / LICENSE.md)
20+
1. **Manifest file** — Reads the license field from:
21+
- `package.json`: `license` field
22+
- `pom.xml`: `<licenses><license><name>` element
23+
- Other ecosystems: varies by ecosystem (some don’t have standard license fields)
24+
25+
2. **LICENSE file** — Searches for `LICENSE`, `LICENSE.md`, or `LICENSE.txt` in the same directory as your manifest
2826

29-
- Look in the **manifest directory** for `LICENSE`, `LICENSE.md`, or `LICENSE.txt`.
30-
- Only searches the same directory as the manifest (not parent directories or git root).
31-
- Read file content; try to **detect** or **normalize** to an SPDX identifier (e.g. "Apache-2.0") for comparison using local pattern matching or the backend `/licenses/identify` endpoint for more accuracy.
27+
The backend’s license identification API is used for accurate LICENSE file detection.
3228

33-
### Reporting manifest vs file mismatch
29+
### Dependency License Information
3430

35-
- If both manifest and file are present and indicate different licenses → set `licenseSummary.manifestVsFileMismatch: true` and include both values so the user can fix the inconsistency.
36-
- If only one is present, we still expose both `fromManifest` and `fromFile` so the user sees what was found.
31+
Dependency licenses come from the Trustify DA backend, which categorizes them as:
32+
- **PERMISSIVE** (MIT, Apache-2.0, BSD, etc.)
33+
- **WEAK_COPYLEFT** (LGPL, MPL, etc.)
34+
- **STRONG_COPYLEFT** (GPL, AGPL, etc.)
35+
- **UNKNOWN**
36+
37+
### Compatibility Checking
38+
39+
The client checks if dependency licenses are compatible with your project license. For example:
40+
- Permissive project (MIT) + permissive dependencies → ✅ Compatible
41+
- Permissive project (MIT) + strong copyleft dependency (GPL) → ⚠️ Potentially incompatible
3742

38-
## Backend: License Analysis API (v5) and license data in the analysis report
43+
Compatibility results are included in the analysis report’s `licenseSummary`.
3944

40-
The Trustify Dependency Analytics backend provides:
45+
## Configuration
4146

42-
1. **License data in the dependency analysis report** — When using the analysis API (e.g. stack or component analysis), the JSON report includes a **`licenses`** field with license information for all dependencies. This is a **LicensesResponse**: an array of provider results, each with `status`, `summary`, and `packages` (object keyed by purl). Each package has `concluded` (with `identifiers`, `expression`, `name`, `category`) and `evidence`. Categories are: **PERMISSIVE**, **WEAK_COPYLEFT**, **STRONG_COPYLEFT**, **UNKNOWN** (see [OpenAPI v5](https://github.com/guacsec/trustify-da-api-spec/blob/main/api/v5/openapi.yaml)).
47+
### Disable License Checking
4348

44-
2. **License identification endpoint**`POST /licenses/identify` accepts a LICENSE file (binary/text) and returns the identified SPDX license. This is used to accurately identify the project's LICENSE file when more precision is needed than local pattern matching.
49+
License analysis runs automatically during component/stack analysis. To disable it:
4550

46-
3. **License details endpoint**`GET /api/v5/licenses/{spdx}` returns detailed information about a specific license by SPDX identifier, including `category`, `name`, `identifiers`, `expression`, `source`, etc. Used by the CLI `license` command to provide rich license information.
51+
**Environment variable:**
52+
```bash
53+
export TRUSTIFY_DA_LICENSE_CHECK=false
54+
```
4755

48-
The client:
56+
**Programmatic option:**
57+
```javascript
58+
await componentAnalysis(‘pom.xml’, { licenseCheck: false });
59+
```
4960

50-
- When running the license check **after component analysis**, it uses the `result.licenses` data from the analysis response (no extra request needed for dependency licenses).
51-
- For the **project LICENSE file**, it first attempts local pattern matching (fast, synchronous). During the license check (async), it can optionally call `POST /licenses/identify` for more accurate backend-based identification.
52-
- Normalizes the license response into a map of purl → `{ licenses: string[], category? }` and uses the backend **category** for compatibility checking (e.g. project permissive + dependency STRONG_COPYLEFT → incompatible).
61+
### Backend URL
5362

54-
## Component analysis: license incompatibility report
63+
License analysis requires the same backend URL as dependency analysis:
64+
```bash
65+
export TRUSTIFY_DA_BACKEND_URL=https://api.trustify.dev
66+
```
5567

56-
When the user runs **component analysis** (e.g. upon opening a manifest):
68+
## CLI Usage
5769

58-
1. The client builds the SBOM (direct dependencies only) and sends it to the backend for the usual component analysis.
59-
2. By default or unless `TRUSTIFY_DA_LICENSE_CHECK=false` or an option like `licenseCheck: false` is set:
60-
- **Resolve project license** from manifest (synchronous).
61-
- If a LICENSE file exists, optionally call `POST /licenses/identify` to accurately identify it via the backend.
62-
- Set `licenseSummary.manifestVsFileMismatch` if manifest and LICENSE file licenses differ.
63-
- **Extract purls** from the SBOM that was sent (from `provided.content`).
64-
- **Get dependency licenses** from the analysis result’s `licenses` array (already included in the response).
65-
- For each dependency, determine if its license(s) are **compatible** with the project license, using the backend category (PERMISSIVE / WEAK_COPYLEFT / STRONG_COPYLEFT).
66-
- Attach a **license summary** to the component analysis result, e.g.:
67-
- `licenseSummary.projectLicenseFromManifest`
68-
- `licenseSummary.projectLicenseFromFile`
69-
- `licenseSummary.manifestVsFileMismatch`
70-
- `licenseSummary.incompatibleDependencies`: `[{ purl, licenses, category, reason }]`
71-
- `licenseSummary.dependencyLicenses`: `[{ purl, licenses, category }]`
72-
- So the user can see which dependencies have licenses incompatible with the project license.
70+
### Get License Information
7371

74-
The client uses the backend **category** from the analysis report’s `licenses` field, plus a small in-client compatibility matrix (e.g. permissive project + STRONG_COPYLEFT dependency → incompatible).
72+
```bash
73+
exhort license path/to/pom.xml
74+
```
7575

76-
## Disabling the license check in component analysis
76+
**Example output:**
77+
```json
78+
{
79+
"projectLicense": {
80+
"fromManifest": "Apache-2.0",
81+
"fromFile": "Apache-2.0",
82+
"mismatch": false
83+
},
84+
"dependencies": {
85+
"pkg:maven/com.google.guava/guava@32.1.0": {
86+
"licenses": ["Apache-2.0"],
87+
"category": "PERMISSIVE",
88+
"compatible": true
89+
},
90+
"pkg:maven/org.postgresql/postgresql@42.6.0": {
91+
"licenses": ["BSD-2-Clause"],
92+
"category": "PERMISSIVE",
93+
"compatible": true
94+
}
95+
}
96+
}
97+
```
7798

78-
- **Environment variable**: `TRUSTIFY_DA_LICENSE_CHECK=false`
79-
- **Option**: `opts.licenseCheck = false` when calling `client.componentAnalysis(manifest, opts)`
99+
## Analysis Report Fields
80100

81-
By default (unless disabled), after the component analysis response is received, the client:
82-
1. Resolves the project license from manifest and LICENSE file (with optional backend identification for LICENSE file)
83-
2. Extracts dependency licenses from `result.licenses` in the analysis response
84-
3. Computes compatibility and attaches a `licenseSummary` to the report
101+
When license checking is enabled, the analysis report includes:
85102

86-
## API surface (client)
87-
88-
- **Project license resolution** (local, synchronous):
89-
- `getProjectLicense(manifestPath, opts)``{ fromManifest, fromFile, mismatch }`
90-
- `getProjectLicenseFromManifestOnly(manifestPath, opts)``{ fromManifest, fromFile: null, mismatch: false }`
91-
- `findLicenseFilePath(manifestPath)``string|null` (path to LICENSE file)
92-
93-
- **Backend license identification and details**:
94-
- `identifyLicenseViaBackend(licenseFilePath, backendUrl, opts)``Promise<string|null>` (SPDX id from `POST /licenses/identify`)
95-
- `getLicenseDetails(spdxId, backendUrl, opts)``Promise<Object|null>` (detailed license info from `GET /api/v5/licenses/{spdx}`, includes category, name, identifiers, etc.)
96-
97-
- **Dependency licenses from analysis report**:
98-
- `licenseMapFromAnalysisReport(analysisReport, purls?)``Map<purl, { licenses, category? }>`
99-
- Extracts license data from the analysis response's `licenses` field (no extra request)
100-
101-
- **Full license check** (for component analysis):
102-
- `runLicenseCheck(sbomContent, manifestPath, backendUrl, opts, analysisResult)``Promise<LicenseSummary>`
103-
- Uses `analysisResult.licenses` for dependency licenses. Optionally calls `POST /licenses/identify` for accurate project LICENSE file identification. Used inside `componentAnalysis()` when license check is enabled; result is attached as `report.licenseSummary`.
104-
105-
- **Compatibility checking**:
106-
- `getCompatibility(projectLicense, dependencyLicenses[], dependencyCategory?)``'compatible' | 'incompatible' | 'unknown'`
107-
108-
- **Utility** (typically not needed):
109-
- `getLicensesByPurl(purls, backendUrl, opts)``Promise<Map<purl, { licenses, category? }>>` (standalone call to `POST /api/v5/licenses`)
110-
111-
Options (e.g. proxy, token) are passed through to the backend fetch in the same way as for analysis requests.
103+
```javascript
104+
{
105+
// ... standard analysis fields ...
106+
"licenseSummary": {
107+
"projectLicenseFromManifest": "Apache-2.0",
108+
"projectLicenseFromFile": "Apache-2.0",
109+
"manifestVsFileMismatch": false,
110+
"incompatibleDependencies": [
111+
{
112+
"purl": "pkg:maven/org.example/gpl-lib@1.0.0",
113+
"licenses": ["GPL-3.0"],
114+
"category": "STRONG_COPYLEFT",
115+
"reason": "Dependency license(s) are incompatible with the project license."
116+
}
117+
],
118+
"dependencyLicenses": [
119+
{ "purl": "...", "licenses": [...], "category": "..." }
120+
]
121+
}
122+
}
123+
```
124+
125+
## Common Scenarios
126+
127+
### Mismatch Between Manifest and LICENSE File
128+
129+
If your `package.json` says `"license": "MIT"` but your LICENSE file contains Apache-2.0 text:
130+
```json
131+
{
132+
"projectLicenseFromManifest": "MIT",
133+
"projectLicenseFromFile": "Apache-2.0",
134+
"manifestVsFileMismatch": true
135+
}
136+
```
137+
138+
**Action:** Update your manifest or LICENSE file to match.
139+
140+
### Incompatible Dependencies
141+
142+
If you have a permissive-licensed project (MIT, Apache) but depend on GPL-licensed libraries, they’ll appear in `incompatibleDependencies`.
143+
144+
**Action:** Review the flagged dependencies and consider:
145+
- Finding alternative libraries with compatible licenses
146+
- Consulting legal counsel if the dependency is necessary
147+
- Understanding how you’re using the dependency (linking, distribution, etc.)
148+
149+
## SBOM Integration
150+
151+
Project license information is automatically included in generated SBOMs (CycloneDX format) in the root component’s `licenses` field.

src/analysis.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@ import { EOL } from "os";
44

55
import { HttpsProxyAgent } from "https-proxy-agent";
66

7-
import { generateImageSBOM, parseImageRef } from "./oci_image/utils.js";
87
import { runLicenseCheck } from "./license/index.js";
9-
import { getCustom, getTokenHeaders } from "./tools.js";
10-
import { TRUSTIFY_DA_OPERATION_TYPE_HEADER, TRUSTIFY_DA_PACKAGE_MANAGER_HEADER } from "./tools.js";
8+
import { generateImageSBOM, parseImageRef } from "./oci_image/utils.js";
9+
import { getCustom, getTokenHeaders , TRUSTIFY_DA_OPERATION_TYPE_HEADER, TRUSTIFY_DA_PACKAGE_MANAGER_HEADER } from "./tools.js";
1110

1211
export default { requestComponent, requestStack, requestImages, validateToken }
1312

src/cli.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ import * as path from "path";
55
import yargs from 'yargs'
66
import { hideBin } from 'yargs/helpers'
77

8-
import client from './index.js'
98
import { getProjectLicense, getLicenseDetails } from './license/index.js'
109

10+
import client from './index.js'
11+
1112

1213
// command for component analysis take manifest type and content
1314
const component = {
@@ -203,7 +204,7 @@ const license = {
203204

204205
// Build LicenseInfo objects
205206
const buildLicenseInfo = async (spdxId) => {
206-
if (!spdxId) return null
207+
if (!spdxId) {return null}
207208

208209
const licenseInfo = { spdxId }
209210

src/cyclone_dx_sbom.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,11 @@ function getComponent(component, type, scope, licenses) {
4343
componentObject = component
4444
}
4545

46-
// Add licenses if provided (CycloneDX format)
46+
// Add licenses if provided (CycloneDX format). Callers must provide valid SPDX identifiers.
4747
if (licenses) {
4848
const licenseArray = Array.isArray(licenses) ? licenses : [licenses];
4949
componentObject.licenses = licenseArray.map(lic => {
5050
if (typeof lic === 'string') {
51-
// If it's a string, treat it as an SPDX id or name
5251
return { license: { id: lic } };
5352
}
5453
return lic;

src/license/compatibility.js

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,37 +13,38 @@
1313
* @returns {'compatible'|'incompatible'|'unknown'}
1414
*/
1515
export function getCompatibility(projectLicense, dependencyLicenses, dependencyCategory) {
16-
if (!projectLicense && !dependencyLicenses?.length) return 'unknown';
16+
if (!projectLicense) {return 'unknown';}
17+
if (!dependencyLicenses?.length) {return 'unknown';}
1718

1819
// Use backend category when available (from API v5 licenses / analysis report)
1920
const cat = String(dependencyCategory || '').toUpperCase();
2021
if (cat === 'STRONG_COPYLEFT') {
2122
const proj = projectLicense ? normalize(projectLicense) : '';
22-
if (isPermissive(proj)) return 'incompatible';
23-
if (isCopyleft(proj)) return 'unknown';
23+
if (isPermissive(proj)) {return 'incompatible';}
24+
if (isCopyleft(proj)) {return 'unknown';}
2425
return 'incompatible';
2526
}
2627
if (cat === 'WEAK_COPYLEFT') {
2728
const proj = projectLicense ? normalize(projectLicense) : '';
28-
if (isPermissive(proj)) return 'unknown'; // weak copyleft often acceptable when used as library
29+
if (isPermissive(proj)) {return 'unknown';} // weak copyleft often acceptable when used as library
2930
return 'unknown';
3031
}
31-
if (cat === 'PERMISSIVE' && (!projectLicense || isPermissive(normalize(projectLicense)))) return 'compatible';
32+
if (cat === 'PERMISSIVE' && (!projectLicense || isPermissive(normalize(projectLicense)))) {return 'compatible';}
3233

33-
if (!projectLicense || !dependencyLicenses?.length) return 'unknown';
34+
if (!projectLicense || !dependencyLicenses?.length) {return 'unknown';}
3435

3536
const proj = normalize(projectLicense);
3637
const depSet = new Set(dependencyLicenses.map(normalize).filter(Boolean));
3738

3839
// Same license or both permissive -> compatible
39-
if (depSet.has(proj)) return 'compatible';
40-
if (isPermissive(proj) && [...depSet].every(isPermissive)) return 'compatible';
40+
if (depSet.has(proj)) {return 'compatible';}
41+
if (isPermissive(proj) && [...depSet].every(isPermissive)) {return 'compatible';}
4142

4243
// Project is permissive; dependency is copyleft -> flag for user awareness
43-
if (isPermissive(proj) && [...depSet].some(isCopyleft)) return 'incompatible';
44+
if (isPermissive(proj) && [...depSet].some(isCopyleft)) {return 'incompatible';}
4445

4546
// Project is copyleft; dependency is different copyleft or proprietary -> unknown / depends on linking
46-
if (isCopyleft(proj)) return 'unknown';
47+
if (isCopyleft(proj)) {return 'unknown';}
4748

4849
return 'unknown';
4950
}

src/license/index.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
* License resolution and dependency license compatibility for component analysis.
33
*/
44

5-
import { getProjectLicense, findLicenseFilePath, identifyLicenseViaBackend } from './project_license.js';
5+
import { getProjectLicense, findLicenseFilePath, identifyLicense } from './project_license.js';
66
import { licenseMapFromAnalysisReport } from './licenses_api.js';
77
import { getCompatibility } from './compatibility.js';
88

9-
export { getProjectLicense, getProjectLicenseFromManifest, findLicenseFilePath, identifyLicenseViaBackend } from './project_license.js';
9+
export { getProjectLicense, getProjectLicenseFromManifest, findLicenseFilePath, identifyLicense as identifyLicenseViaBackend } from './project_license.js';
1010
export { licenseMapFromAnalysisReport, normalizeLicensesResponse, getLicensesByPurl, getLicenseDetails } from './licenses_api.js';
1111
export { getCompatibility } from './compatibility.js';
1212

@@ -58,7 +58,7 @@ export async function runLicenseCheck(sbomContent, manifestPath, backendUrl, opt
5858
const licenseFilePath = findLicenseFilePath(manifestPath);
5959
if (licenseFilePath && backendUrl) {
6060
try {
61-
projectLicenseFromFileBackend = await identifyLicenseViaBackend(licenseFilePath, backendUrl, opts);
61+
projectLicenseFromFileBackend = await identifyLicense(licenseFilePath, backendUrl, opts);
6262
} catch {
6363
// Fall back to local detection (already in projectLicense.fromFile)
6464
}

0 commit comments

Comments
 (0)