Skip to content

feat: implement license resolution and identification#403

Merged
ruromero merged 8 commits into
guacsec:mainfrom
ruromero:oss-license
Mar 11, 2026
Merged

feat: implement license resolution and identification#403
ruromero merged 8 commits into
guacsec:mainfrom
ruromero:oss-license

Conversation

@ruromero

@ruromero ruromero commented Mar 10, 2026

Copy link
Copy Markdown
Collaborator

Description

  • License Analysis (Opt-Out by Default): License compatibility checking is now enabled by default. Users can disable it with TRUSTIFY_DA_LICENSE_CHECK=false
  • Project License Resolution: Automatically detects project license from:
    • Manifest files (package.json, pom.xml)
    • LICENSE files (LICENSE, LICENSE.md, LICENSE.txt) in the same directory as the manifest
    • Backend license identification API for accurate detection
  • License Compatibility Analysis: Checks if dependency licenses are compatible with the project license based on the categories calculated in the backend:
    • PERMISSIVE licenses (MIT, Apache-2.0, BSD, etc.)
    • WEAK_COPYLEFT licenses (LGPL, MPL)
    • STRONG_COPYLEFT licenses (GPL, AGPL)
  • SBOM Enhancement: Project license is now included in the root component of generated SBOMs (CycloneDX format)
  • New CLI Command: exhort license <manifest> returns machine-readable JSON with:
    • Project license from manifest and LICENSE file
    • License mismatch detection
    • Dependency licenses with compatibility status

Related issues (if any):

Checklist

  • I have followed this repository's contributing guidelines.
  • I will adhere to the project's code of conduct.

@ruromero
ruromero requested a review from Strum355 March 10, 2026 13:16
@qodo-code-review

Copy link
Copy Markdown

Review Summary by Qodo

Implement license resolution and dependency compatibility checking

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Implement license resolution from manifest and LICENSE files
• Add license compatibility checking for dependencies in component analysis
• Create new CLI command for project license information retrieval
• Include project license in generated SBOM root component
• Refactor header constants to tools module for reusability
Diagram
flowchart LR
  A["Project License<br/>Resolution"] -->|manifest + file| B["License<br/>Mismatch<br/>Detection"]
  C["Component<br/>Analysis"] -->|SBOM| D["Extract<br/>Dependencies"]
  D -->|purls| E["Backend<br/>License API"]
  E -->|licenses + category| F["Compatibility<br/>Check"]
  A -->|license| G["SBOM Root<br/>Component"]
  F -->|incompatible list| H["License<br/>Summary"]
  I["CLI License<br/>Command"] -->|manifest path| A
  A -->|details| I
Loading

Grey Divider

File Changes

1. src/license/index.js ✨ Enhancement +124/-0

Main license check orchestration module

src/license/index.js


2. src/license/project_license.js ✨ Enhancement +221/-0

Project license resolution from manifest and files

src/license/project_license.js


3. src/license/licenses_api.js ✨ Enhancement +142/-0

Backend license analysis API client integration

src/license/licenses_api.js


View more (19)
4. src/license/compatibility.js ✨ Enhancement +65/-0

License compatibility matrix and checking logic

src/license/compatibility.js


5. src/analysis.js ✨ Enhancement +15/-50

Integrate license check into component analysis

src/analysis.js


6. src/cli.js ✨ Enhancement +74/-1

Add new license CLI command for manifest analysis

src/cli.js


7. src/cyclone_dx_sbom.js ✨ Enhancement +19/-4

Add license support to SBOM root component

src/cyclone_dx_sbom.js


8. src/index.js ✨ Enhancement +3/-0

Export license resolution and API functions

src/index.js


9. src/tools.js ✨ Enhancement +46/-0

Refactor header constants and token header function

src/tools.js


10. src/sbom.js ✨ Enhancement +3/-2

Pass license parameter to SBOM root component

src/sbom.js


11. src/providers/base_javascript.js ✨ Enhancement +7/-2

Include project license in JavaScript SBOM root

src/providers/base_javascript.js


12. src/providers/golang_gomodules.js ✨ Enhancement +4/-1

Include project license in Go module SBOM root

src/providers/golang_gomodules.js


13. src/providers/java_gradle.js ✨ Enhancement +7/-2

Include project license in Gradle SBOM root

src/providers/java_gradle.js


14. src/providers/java_maven.js ✨ Enhancement +10/-4

Include project license in Maven SBOM root

src/providers/java_maven.js


15. src/providers/python_pip.js ✨ Enhancement +7/-2

Include project license in Python SBOM root

src/providers/python_pip.js


16. test/exhort-backend-utils.test.js 🧪 Tests +1/-1

Update import path for getTokenHeaders function

test/exhort-backend-utils.test.js


17. test/providers/tst_manifests/maven/pom_deps_with_ignore_version_from_property/pom.xml 🧪 Tests +10/-1

Add license declaration to test manifest

test/providers/tst_manifests/maven/pom_deps_with_ignore_version_from_property/pom.xml


18. test/providers/tst_manifests/maven/pom_deps_with_ignore_version_from_property/component_analysis_expected_sbom.json 🧪 Tests +16/-2

Update expected SBOM with license in root component

test/providers/tst_manifests/maven/pom_deps_with_ignore_version_from_property/component_analysis_expected_sbom.json


19. test/providers/tst_manifests/maven/pom_deps_with_ignore_version_from_property/stack_analysis_expected_sbom.json 🧪 Tests +16/-2

Update expected SBOM with license in root component

test/providers/tst_manifests/maven/pom_deps_with_ignore_version_from_property/stack_analysis_expected_sbom.json


20. README.md 📝 Documentation +13/-1

Document license command and license resolution feature

README.md


21. docs/license-resolution-and-compliance.md 📝 Documentation +111/-0

Comprehensive guide for license resolution and compliance

docs/license-resolution-and-compliance.md


22. package.json Dependencies +1/-1

Update trustify-da-api-model dependency version

package.json


Grey Divider

Qodo Logo

@qodo-code-review

qodo-code-review Bot commented Mar 10, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📎 Requirement gaps (1)

Grey Divider


Action required

1. SBOM license always uses id 📎 Requirement gap ✓ Correctness
Description
The SBOM generator always serializes provided license strings into CycloneDX licenses[].license.id
even when the value may be a non-SPDX name/expression (e.g., Maven ...). This can make project
license information unusable/unparseable for backend ingestion.
Code

src/cyclone_dx_sbom.js[R46-55]

+	// Add licenses if provided (CycloneDX format)
+	if (licenses) {
+		const licenseArray = Array.isArray(licenses) ? licenses : [licenses];
+		componentObject.licenses = licenseArray.map(lic => {
+			if (typeof lic === 'string') {
+				// If it's a string, treat it as an SPDX id or name
+				return { license: { id: lic } };
+			}
+			return lic;
+		});
Evidence
PR Compliance ID 1 requires the SBOM to include project license information in an
ingestible/parseable way. The new logic unconditionally writes any string as license.id, which is
intended for SPDX identifiers; non-SPDX values will likely be mis-parsed or rejected by downstream
tooling.

SBOM includes project-level license information
src/cyclone_dx_sbom.js[46-55]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The SBOM currently writes any license string into CycloneDX `licenses[].license.id`, which should be reserved for SPDX license IDs. When the manifest provides a license name (common in Maven) or a sentinel like `SEE LICENSE IN FILE`, the SBOM will carry an invalid `id`, risking backend ingestion/parsing failures.
## Issue Context
Project license strings come from multiple ecosystems and may not be SPDX-normalized. CycloneDX supports both `license.id` (SPDX) and `license.name` (free-form), plus `expression`.
## Fix Focus Areas
- src/cyclone_dx_sbom.js[46-55]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Backend ID headers broken🐞 Bug ✓ Correctness
Description
identifyLicenseViaBackend() calls getTokenHeaders() but src/license/project_license.js never imports
it, so backend identification always falls back to local detection (and won’t send auth headers even
when configured). This makes backend-based LICENSE identification effectively non-functional and
silently reduces accuracy.
Code

src/license/project_license.js[R145-156]

+export async function identifyLicenseViaBackend(licenseFilePath, backendUrl, opts = {}) {
+	try {
+		const fileContent = fs.readFileSync(licenseFilePath);
+		const url = `${backendUrl.replace(/\/$/, '')}/licenses/identify`;
+		const tokenHeaders = getTokenHeaders(opts);
+		const fetchOptions = {
+			method: 'POST',
+			headers: {
+				'Content-Type': 'application/octet-stream',
+				...tokenHeaders,
+			},
+			body: fileContent,
Evidence
project_license.js references getTokenHeaders() without importing it, so the call will throw and be
caught by the surrounding try/catch, returning null and preventing backend identification from ever
succeeding.

src/license/project_license.js[9-12]
src/license/project_license.js[145-155]
src/tools.js[175-203]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`identifyLicenseViaBackend()` references `getTokenHeaders()` but `src/license/project_license.js` does not import it. This causes backend license identification to always fail and fall back to local detection.
### Issue Context
`getTokenHeaders` was moved to `src/tools.js` and is used elsewhere (e.g., `src/license/licenses_api.js`, `src/analysis.js`). This function should also be used for `/licenses/identify` calls.
### Fix Focus Areas
- src/license/project_license.js[9-12]
- src/license/project_license.js[145-156]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. False incompatible when unknown🐞 Bug ✓ Correctness
Description
getCompatibility() returns 'incompatible' for STRONG_COPYLEFT dependencies even when projectLicense
is null, causing runLicenseCheck() to report incompatibleDependencies with no project license
baseline. This produces incorrect compliance output (false positives) whenever the project license
can’t be resolved.
Code

src/license/compatibility.js[R15-25]

+export function getCompatibility(projectLicense, dependencyLicenses, dependencyCategory) {
+	if (!projectLicense && !dependencyLicenses?.length) return 'unknown';
+
+	// Use backend category when available (from API v5 licenses / analysis report)
+	const cat = String(dependencyCategory || '').toUpperCase();
+	if (cat === 'STRONG_COPYLEFT') {
+		const proj = projectLicense ? normalize(projectLicense) : '';
+		if (isPermissive(proj)) return 'incompatible';
+		if (isCopyleft(proj)) return 'unknown';
+		return 'incompatible';
+	}
Evidence
When dependencyCategory is STRONG_COPYLEFT and projectLicense is falsy, getCompatibility()
normalizes it to an empty string and returns 'incompatible'. runLicenseCheck() may pass a null
project license and will record incompatibilities based on that return value.

src/license/compatibility.js[15-25]
src/license/index.js[99-114]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`getCompatibility()` can return `&amp;#x27;incompatible&amp;#x27;` even when `projectLicense` is missing. This leads `runLicenseCheck()` to emit `incompatibleDependencies` without a known project license baseline.
### Issue Context
`runLicenseCheck()` explicitly sets `projectLicenseForCheck` to `null` when neither manifest nor LICENSE file yields a license.
### Fix Focus Areas
- src/license/compatibility.js[15-33]
- src/license/index.js[99-114]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
4. JS SBOM fixtures outdated 🐞 Bug ⛯ Reliability
Description
Base_javascript now injects the manifest license into the SBOM root component, but existing
npm/js-common expected SBOM fixtures omit the new licenses field. This will break strict SBOM JSON
comparisons in tests for JS providers.
Code

src/providers/base_javascript.js[R179-185]

  	let mainComponent = toPurl(purlType, this.#manifest.name, this.#manifest.version);
+		const projectLicense = getProjectLicenseFromManifest(this.#manifest.manifestPath, opts);
+		const license = projectLicense.fromManifest;

  	let sbom = new Sbom();
-		sbom.addRoot(mainComponent);
+		sbom.addRoot(mainComponent, license);
Evidence
The JS provider now adds root licenses from package.json, and at least one test fixture package.json
declares a license (ISC) while its expected SBOM does not include any licenses field under the
root component.

src/providers/base_javascript.js[179-185]
test/providers/tst_manifests/npm/package_json_deps_with_exhortignore_object/package.json[14-18]
test/providers/tst_manifests/npm/package_json_deps_with_exhortignore_object/stack_expected_sbom.json[7-13]
test/providers/tst_manifests/js-common/package_json_deps_with_exhortignore_object/component_expected_sbom.json[7-13]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
JS providers now add the manifest `license` into the SBOM root component. Existing expected SBOM JSON fixtures for JS manifests (which already declare a license) don’t include this field and will fail comparisons.
### Issue Context
Example fixture `package.json` contains `&amp;quot;license&amp;quot;: &amp;quot;ISC&amp;quot;`, but the expected SBOM root component lacks `licenses`.
### Fix Focus Areas
- src/providers/base_javascript.js[176-189]
- test/providers/tst_manifests/npm/package_json_deps_with_exhortignore_object/stack_expected_sbom.json[5-22]
- test/providers/tst_manifests/js-common/package_json_deps_with_exhortignore_object/component_expected_sbom.json[5-22]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment thread src/cyclone_dx_sbom.js Outdated
Comment thread src/license/project_license.js Outdated
Comment thread src/license/compatibility.js Outdated
Comment thread src/providers/base_javascript.js
ruromero and others added 3 commits March 10, 2026 15:55
Signed-off-by: Ruben Romero Montes <rromerom@redhat.com>

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Ruben Romero Montes <rromerom@redhat.com>

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Ruben Romero Montes <rromerom@redhat.com>

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@ruromero
ruromero force-pushed the oss-license branch 2 times, most recently from 96a0e8c to 9c2b64f Compare March 10, 2026 15:35
Signed-off-by: Ruben Romero Montes <rromerom@redhat.com>
Comment thread src/sbom.js
Comment thread src/index.js Outdated
Comment thread src/license/licenses_api.js Outdated
Comment thread src/license/index.js Outdated
…ge consistent

Signed-off-by: Ruben Romero Montes <rromerom@redhat.com>
@ruromero
ruromero requested a review from Strum355 March 10, 2026 22:00
@ruromero

Copy link
Copy Markdown
Collaborator Author

@Strum355 I have refactored the license files to include them in the providers. I've also updated some leftovers from the removed providers and all the endpoints to use /v5

Comment thread src/providers/golang_gomodules.js
Signed-off-by: Ruben Romero Montes <rromerom@redhat.com>
Comment thread src/license/index.js
Comment thread src/providers/base_java.js
Comment thread src/license/licenses_api.js Outdated
Strum355
Strum355 previously approved these changes Mar 11, 2026

@Strum355 Strum355 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some non-blocking issues but LGTM 👍

Signed-off-by: Ruben Romero Montes <rromerom@redhat.com>
@ruromero
ruromero enabled auto-merge (squash) March 11, 2026 12:01
@ruromero
ruromero merged commit 0e9ba23 into guacsec:main Mar 11, 2026
4 checks passed
Comment thread src/license/project_license.js
Comment thread src/license/project_license.js
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OSS License identification and analysis

3 participants