Skip to content

feat: fall back to LICENSE file#409

Merged
ruromero merged 2 commits into
guacsec:mainfrom
ruromero:oss-license-from-file
Mar 12, 2026
Merged

feat: fall back to LICENSE file#409
ruromero merged 2 commits into
guacsec:mainfrom
ruromero:oss-license-from-file

Conversation

@ruromero

Copy link
Copy Markdown
Collaborator

Description

Detect licenses from the manifest and fallback to LICENSE file when not available.

Related issues (if any):

Checklist

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

Additional information

I have also created a compile:dev profile to generate the map files

Signed-off-by: Ruben Romero Montes <rromerom@redhat.com>
Co-authored-by: Claude Sonnet <noreply@anthropic.com>
@ruromero
ruromero requested a review from Strum355 March 12, 2026 09:17
@qodo-code-review

Copy link
Copy Markdown

Review Summary by Qodo

Implement LICENSE file fallback for all package managers with SPDX detection

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Implement LICENSE file fallback for all package managers
• Refactor license utilities into separate module to avoid circular dependencies
• Add SPDX detection from LICENSE file content with common license patterns
• Update all providers to use fallback mechanism for license detection
• Enhance documentation with license detection strategy and SBOM integration
Diagram
flowchart LR
  A["Manifest License"] -->|Present| B["Use Manifest License"]
  A -->|Absent| C["Check LICENSE File"]
  C -->|Found| D["Detect SPDX Pattern"]
  D -->|Match| E["Return SPDX ID"]
  D -->|No Match| F["Return First Line"]
  C -->|Not Found| G["Return Null"]
  B --> H["Include in SBOM"]
  E --> H
  F --> H
  G --> H
Loading

Grey Divider

File Changes

1. src/license/license_utils.js ✨ Enhancement +127/-0

New utility module for license file handling

src/license/license_utils.js


2. src/license/project_license.js Refactoring +15/-64

Refactor to use license utilities module

src/license/project_license.js


3. src/license/index.js Refactoring +4/-4

Update imports and exports for new structure

src/license/index.js


View more (12)
4. src/license/compatibility.js Refactoring +0/-53

File removed, functionality moved to license_utils

src/license/compatibility.js


5. src/index.js Refactoring +1/-1

Update exported function name for consistency

src/index.js


6. src/providers/base_javascript.js ✨ Enhancement +7/-6

Add LICENSE file fallback for JavaScript projects

src/providers/base_javascript.js


7. src/providers/java_maven.js ✨ Enhancement +10/-9

Add LICENSE file fallback for Maven projects

src/providers/java_maven.js


8. src/providers/java_gradle.js ✨ Enhancement +2/-1

Add LICENSE file fallback for Gradle projects

src/providers/java_gradle.js


9. src/providers/golang_gomodules.js ✨ Enhancement +2/-1

Add LICENSE file fallback for Go projects

src/providers/golang_gomodules.js


10. src/providers/python_pip.js ✨ Enhancement +2/-1

Add LICENSE file fallback for Python projects

src/providers/python_pip.js


11. test/providers/license_fallback.test.js 🧪 Tests +70/-0

New test suite for LICENSE file fallback feature

test/providers/license_fallback.test.js


12. docs/license-resolution-and-compliance.md 📝 Documentation +18/-5

Document license detection strategy and fallback mechanism

docs/license-resolution-and-compliance.md


13. README.md 📝 Documentation +30/-0

Add license detection section with fallback explanation

README.md


14. package.json ⚙️ Configuration changes +2/-1

Add compile:dev script for source maps

package.json


15. tsconfig.dev.json ⚙️ Configuration changes +6/-0

New TypeScript config for development with source maps

tsconfig.dev.json


Grey Divider

Qodo Logo

@qodo-code-review

qodo-code-review Bot commented Mar 12, 2026

Copy link
Copy Markdown

Code Review by Qodo

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

Grey Divider


Action required

1. readLicenseFile() may return non-SPDX 📎 Requirement gap ✓ Correctness
Description
readLicenseFile() can return the first line of LICENSE content when SPDX detection fails, which
may not be a valid SPDX identifier. When this value is written into the SBOM as a CycloneDX
license.id, the backend may not be able to ingest/parse the project license correctly.
Code

src/license/license_utils.js[R56-65]

+export function readLicenseFile(manifestPath) {
+	const licenseFilePath = findLicenseFilePath(manifestPath);
+	if (!licenseFilePath) { return null; }
+
+	try {
+		const content = fs.readFileSync(licenseFilePath, 'utf-8');
+		return detectSpdxFromText(content) || content.split('\n')[0]?.trim() || null;
+	} catch {
+		return null;
+	}
Evidence
Compliance requires project-level license info to be included in the SBOM in a backend-ingestible
format. The new fallback returns arbitrary first-line text when SPDX detection fails, while SBOM
generation encodes any provided string as a CycloneDX license.id (SPDX id), which can make the
SBOM license field unusable for the backend.

Include project license information in generated SBOM
src/license/license_utils.js[56-65]
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
`readLicenseFile()` may return non-SPDX text (e.g., first line of LICENSE) and providers pass that into SBOM root licenses, which CycloneDX serialization encodes as `license.id` (SPDX id). This can make the project license in the generated SBOM unparseable by the backend.

## Issue Context
- `readLicenseFile()` falls back to `content.split(&#x27;\n&#x27;)[0]` when `detectSpdxFromText()` fails.
- CycloneDX SBOM builder maps any string license to `{ license: { id: &lt;string&gt; } }`, which implies a valid SPDX identifier.

## Fix Focus Areas
- src/license/license_utils.js[56-65]
- src/cyclone_dx_sbom.js[46-55]

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


2. License source conflated 🐞 Bug ✓ Correctness
Description
Several providers now implement readLicenseFromManifest() with LICENSE-file fallback, so
getProjectLicense().fromManifest can be LICENSE-derived rather than manifest-derived, making the
CLI’s "manifestLicense" output misleading and collapsing the intended manifest-vs-file distinction.
This also causes runLicenseCheck() to treat LICENSE-derived values as the manifest license when
building its projectLicense.manifest vs projectLicense.file objects.
Code

src/providers/base_javascript.js[R159-174]

	readLicenseFromManifest(manifestPath) {
+		let manifestLicense;
		try {
			const content = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
			if (typeof content.license === 'string') {
-				return content.license.trim() || null;
-			}
-			if (Array.isArray(content.licenses) && content.licenses.length > 0) {
+				manifestLicense = content.license.trim() || null;
+			} else if (Array.isArray(content.licenses) && content.licenses.length > 0) {
				const first = content.licenses[0];
				const name = first.type || first.name;
-				return typeof name === 'string' ? name.trim() : null;
+				manifestLicense = (typeof name === 'string' ? name.trim() : null);
			}
-			return null;
		} catch {
-			return null;
+			manifestLicense = null;
		}
+		return getLicense(manifestLicense, manifestPath);
	}
Evidence
getProjectLicense() explicitly intends to report two sources—provider.readLicenseFromManifest()
(stored as fromManifest) and an independent LICENSE-file read (stored as fromFile)—and downstream
code/CLI labels these as “manifest” vs “file”. But providers were changed so
readLicenseFromManifest() itself can read the LICENSE file (directly or via getLicense()), meaning
fromManifest is no longer guaranteed to be manifest-sourced, so the CLI/reporting semantics are
incorrect.

src/license/project_license.js[25-37]
src/providers/base_javascript.js[159-174]
src/providers/golang_gomodules.js[38-45]
src/providers/python_pip.js[35-42]
src/providers/java_gradle.js[73-80]
src/cli.js[172-176]
src/cli.js[230-234]

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

## Issue description
`getProjectLicense()` is intended to return two distinct sources: license-from-manifest (`fromManifest`) and license-from-LICENSE-file (`fromFile`). After this PR, multiple providers implement `readLicenseFromManifest()` with LICENSE-file fallback, so `fromManifest` can be LICENSE-derived, making CLI/runLicenseCheck reporting misleading.

## Issue Context
- `getProjectLicense()` calls `provider.readLicenseFromManifest()` and independently calls `readLicenseFile()`.
- Providers now call `readLicenseFile()` (or `getLicense()` which falls back to `readLicenseFile()`) inside `readLicenseFromManifest()`.
- CLI labels `fromManifest` as `manifestLicense` and `fromFile` as `fileLicense`.

## Fix Focus Areas
- src/license/project_license.js[25-37]
- src/cli.js[172-176]
- src/cli.js[230-234]
- src/providers/base_javascript.js[159-174]
- src/providers/java_maven.js[74-91]
- src/providers/java_gradle.js[73-80]
- src/providers/golang_gomodules.js[38-45]
- src/providers/python_pip.js[35-42]

## Notes
A robust approach is:
1) Make `readLicenseFromManifest()` return manifest-only (no fallback).
2) Add a separate helper for SBOM root license/effective license (manifest-or-LICENSE).
3) Keep CLI/runLicenseCheck using the strict manifest/file separation, or rename output fields to reflect the new semantics explicitly (e.g., `primaryLicense`/`licenseFromFile`).

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


3. Export rename breaks consumers 🐞 Bug ✓ Correctness
Description
The public entrypoint no longer exports identifyLicenseViaBackend and now exports identifyLicense,
which will break existing consumers importing the old named export at runtime. Because package.json
points main/module/types to dist/src/index.*, this rename changes the published API surface.
Code

src/index.js[R10-13]

export { parseImageRef } from "./oci_image/utils.js";
export { ImageRef } from "./oci_image/images.js";
-export { getProjectLicense, findLicenseFilePath, identifyLicenseViaBackend, getLicenseDetails, licensesFromReport, normalizeLicensesResponse, runLicenseCheck, getCompatibility } from "./license/index.js";
+export { getProjectLicense, findLicenseFilePath, identifyLicense, getLicenseDetails, licensesFromReport, normalizeLicensesResponse, runLicenseCheck, getCompatibility } from "./license/index.js";
Evidence
The package entrypoint (dist/src/index.js) is derived from src/index.js, and it now exports
identifyLicense rather than the previously exported identifyLicenseViaBackend.
src/license/index.js also no longer aliases the export, so consumers using the old name have no
compatibility shim.

src/index.js[10-13]
src/license/index.js[5-11]
package.json[27-31]

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

## Issue description
A previously exported symbol (`identifyLicenseViaBackend`) was removed/renamed to `identifyLicense`. This is a breaking change for consumers.

## Issue Context
- `src/index.js` is the public entrypoint compiled to `dist/src/index.js` (per package.json).
- `src/license/index.js` no longer provides an alias export.

## Fix Focus Areas
- src/index.js[10-13]
- src/license/index.js[5-11]

## Suggested fix
- Add a compatibility export, e.g. in `src/license/index.js`:
 - `export { identifyLicense as identifyLicenseViaBackend } from &#x27;./project_license.js&#x27;;`
 and/or re-export that alias from `src/index.js`.
- If intentional breaking change, bump major version and add migration notes.

ⓘ 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/license/license_utils.js
Comment thread src/providers/base_javascript.js
Comment thread src/index.js
@ruromero
ruromero enabled auto-merge (squash) March 12, 2026 09:31
@ruromero
ruromero merged commit f2c4df7 into guacsec:main Mar 12, 2026
4 checks passed
@ruromero
ruromero deleted the oss-license-from-file branch March 12, 2026 09:36
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

2 participants