|
1 | | -# License resolution and compliance |
| 1 | +# License Resolution and Compliance |
2 | 2 |
|
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. |
4 | 4 |
|
5 | | -## Goals |
| 5 | +## Overview |
6 | 6 |
|
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: |
12 | 8 |
|
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 |
14 | 13 |
|
15 | | -### From the manifest |
| 14 | +## How It Works |
16 | 15 |
|
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 |
24 | 17 |
|
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: |
26 | 19 |
|
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 |
28 | 26 |
|
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. |
32 | 28 |
|
33 | | -### Reporting manifest vs file mismatch |
| 29 | +### Dependency License Information |
34 | 30 |
|
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 |
37 | 42 |
|
38 | | -## Backend: License Analysis API (v5) and license data in the analysis report |
| 43 | +Compatibility results are included in the analysis report’s `licenseSummary`. |
39 | 44 |
|
40 | | -The Trustify Dependency Analytics backend provides: |
| 45 | +## Configuration |
41 | 46 |
|
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 |
43 | 48 |
|
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: |
45 | 50 |
|
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 | +``` |
47 | 55 |
|
48 | | -The client: |
| 56 | +**Programmatic option:** |
| 57 | +```javascript |
| 58 | +await componentAnalysis(‘pom.xml’, { licenseCheck: false }); |
| 59 | +``` |
49 | 60 |
|
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 |
53 | 62 |
|
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 | +``` |
55 | 67 |
|
56 | | -When the user runs **component analysis** (e.g. upon opening a manifest): |
| 68 | +## CLI Usage |
57 | 69 |
|
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 |
73 | 71 |
|
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 | +``` |
75 | 75 |
|
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 | +``` |
77 | 98 |
|
78 | | -- **Environment variable**: `TRUSTIFY_DA_LICENSE_CHECK=false` |
79 | | -- **Option**: `opts.licenseCheck = false` when calling `client.componentAnalysis(manifest, opts)` |
| 99 | +## Analysis Report Fields |
80 | 100 |
|
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: |
85 | 102 |
|
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. |
0 commit comments