Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ kubescape-macos-latest
*.tar.gz
*.zip
*_SBOM.json
*_LICENSE.json
syft
gitleaks_aux_report_*.json
gitleaks_report.json
Expand Down
9 changes: 7 additions & 2 deletions docs/Docusaurus/docs/life_cycle/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ For more information about structure remote config visit [Structure Remote Confi
<td>Free</td>
</tr>
<tr>
<td rowspan="3">ENGINE_IAC</td>
<td rowspan="4">ENGINE_IAC</td>
<td><a href="https://www.checkov.io/">CHECKOV</a></td>
<td>Free</td>
</tr>
Expand Down Expand Up @@ -81,6 +81,11 @@ For more information about structure remote config visit [Structure Remote Confi
<tr>
<td><a href="https://trivy.dev/">TRIVY</a></td>
<td>Free</td>
</tr>
<tr>
<td>ENGINE_LICENSE</td>
<td><a href="https://github.com/CycloneDX/cdxgen">CDXGEN</a></td>
<td>Free</td>
</tr>
<tr>
<td>ENGINE_FUNCTION</td>
Expand All @@ -101,7 +106,7 @@ For more information about structure remote config visit [Structure Remote Confi
### Scan running - (CLI) - Flags

```bash
devsecops-engine-tools --platform_devops ["local","azure","github"] --remote_config_source ["local","azure","github"] --remote_config_repo ["remote_config_repo"] --remote_config_branch ["remote_config_branch"] --module ["engine_iac", "engine_dast", "engine_secret", "engine_dependencies", "engine_container", "engine_risk", "engine_code", "engine_function"] --tool ["nuclei", "bearer", "checkov", "kics", "kubescape", "trufflehog", "gitleaks", "prisma", "trivy", "xray", "dependency_check"] --folder_path ["Folder path scan engine_iac, engine_code, engine_dependencies and engine_secret"] --platform ["k8s","cloudformation","docker", "openapi", "terraform"] --use_secrets_manager ["false", "true"] --use_vulnerability_management ["false", "true"] --send_metrics ["false", "true"] --token_cmdb ["token_cmdb"] --token_vulnerability_management ["token_vulnerability_management"] --token_engine_container ["token_engine_container"] --token_engine_dependencies ["token_engine_dependencies"] --token_external_checks ["token_external_checks"] --xray_mode ["scan", "audit","build-scan"] --image_to_scan ["image_to_scan"] --dast_file_path ["dast_file_path"] --context ["false", "true"] --terraform_repo_root ["terraform_files_repo"] --docker_address ["docker addres to engine_container with Prisma tool"]
devsecops-engine-tools --platform_devops ["local","azure","github"] --remote_config_source ["local","azure","github"] --remote_config_repo ["remote_config_repo"] --remote_config_branch ["remote_config_branch"] --module ["engine_iac", "engine_dast", "engine_secret", "engine_dependencies", "engine_license", "engine_container", "engine_risk", "engine_code", "engine_function"] --tool ["nuclei", "bearer", "checkov", "kics", "kubescape", "trufflehog", "gitleaks", "prisma", "trivy", "xray", "dependency_check", "cdxgen"] --folder_path ["Folder path scan engine_iac, engine_code, engine_dependencies, engine_secret and engine_license"] --platform ["k8s","cloudformation","docker", "openapi", "terraform"] --use_secrets_manager ["false", "true"] --use_vulnerability_management ["false", "true"] --send_metrics ["false", "true"] --token_cmdb ["token_cmdb"] --token_vulnerability_management ["token_vulnerability_management"] --token_engine_container ["token_engine_container"] --token_engine_dependencies ["token_engine_dependencies"] --token_external_checks ["token_external_checks"] --xray_mode ["scan", "audit","build-scan"] --image_to_scan ["image_to_scan"] --dast_file_path ["dast_file_path"] --context ["false", "true"] --terraform_repo_root ["terraform_files_repo"] --docker_address ["docker addres to engine_container with Prisma tool"]
```

### Scan running sample (CLI) - Local
Expand Down
8 changes: 8 additions & 0 deletions docs/Docusaurus/docs/life_cycle/remote_config_structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ sidebar_position: 2
┃ ┗ 📂engine_function
┃ ┗ 📜ConfigTool.json
┃ ┗ 📜Exclusions.json
┃ ┗ 📂engine_license
┃ ┗ 📜ConfigTool.json
```

## **engine_core**
Expand Down Expand Up @@ -104,6 +106,12 @@ Software Composition Analysis (SCA) is a process that detects compromised or vul

For more information about structure remote config for this module, [engine dependencies](../modules/engine_sca/engine_dependencies.md)

### **engine_license**

Software license compliance scanning. Detects denied / unlicensed dependencies in source folders, container images, and SBOMs, and enforces a configurable policy.

For more information about structure remote config for this module, [engine license](../modules/engine_sca/engine_license.md)

## **vulnerability_management**

### **vulnerability_management/cmdb_mapping**
Expand Down
8 changes: 8 additions & 0 deletions docs/Docusaurus/docs/modules/engine_core.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,10 @@ Configuration of the driven adapters in the main layer and management of on/off
"TOOL": "XRAY|DEPENDENCY_CHECK|TRIVY",
"PRIORITY": "STANDARD|DISCREET"
},
"ENGINE_LICENSE": {
"ENABLED": true,
"PRIORITY": "STANDARD|DISCREET"
},
"ENGINE_CODE": {
"ENABLED": true,
"TOOL": "BEARER|KIUWAN",
Expand Down Expand Up @@ -440,6 +444,10 @@ Configuration of the driven adapters in the main layer and management of on/off
- TOOL: XRAY | DEPENDENCY_CHECK | TRIVY
- PRIORITY: STANDARD | DISCREET

- **ENGINE_LICENSE**: Configuration for the engine_license tool
- ENABLED: true or false
- PRIORITY: STANDARD | DISCREET

- **ENGINE_FUNCTION**: Configuration for the engine_function tool
- ENABLED: true or false
- TOOL: PRISMA
Expand Down
184 changes: 184 additions & 0 deletions docs/Docusaurus/docs/modules/engine_sca/engine_license.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
# Module Engine License

## Overview

The `engine_license` module is a **standalone** license-compliance reporter inside the DevSecOps Engine Tools platform. It scans the local repository, generates a fresh CycloneDX SBOM via **CdxGen**, classifies every dependency against a policy declared in remote configuration, and emits a single artifact: `{pipeline_name}_LICENSE.json`.
Comment thread
jcamilomolinar marked this conversation as resolved.
Comment thread
yenner123 marked this conversation as resolved.

Unlike other engines, `engine_license` does **not**:

- Participate in `THRESHOLD` / break-build decisions.
- Use `Exclusions.json`.
- Push findings to vulnerability management.
- Reuse SBOMs from previous runs.
- Scan container images (the artifact is repository-only).

The intent is to ship an audit-friendly, policy-driven JSON that downstream consumers can analyse out-of-band of build pipelines.

> **Why CdxGen?** The module relies on CdxGen because it is the only mainstream SBOM generator that enriches license data by querying the package registry (e.g. Maven Central, npm, NuGet) via its `FETCH_LICENSE` option. Tools such as Syft and Trivy report only locally declared licenses (from lockfiles or existing package metadata) and do not perform active registry lookups, so licenses missing from local manifests (common in Maven/Gradle projects) would not be resolved. This is why `SBOM_MANAGER.CDXGEN.FETCH_LICENSE` must be `true` for the report to be complete.

## Configuration Structure

Only one configuration file is consumed: `engine_sca/engine_license/ConfigTool.json`. There is no `Exclusions.json`; the policy is declared inline.

### ConfigTool.json

```json
{
"LICENSE": {
"LICENSE_POLICY": {
"fail": ["AGPL-*", "SSPL-*"],
"warn": ["BUSL-*", "EPL-*", "LGPL-3.0*"],
"synonyms": {},
"unlicensed_action": "ignore",
"unknown_action": "ignore"
}
}
}
```

#### `LICENSE_POLICY` block

Declarative policy applied to every dependency in the SBOM. **All keys are required** for `engine_license` to produce a report; if `LICENSE_POLICY` is missing, no report is generated.

- **fail**: Glob patterns (case-insensitive `fnmatch`) whose match marks the package as `fail` (severity `critical`).
- **warn**: Glob patterns whose match marks the package as `warn` (severity `medium`).
- **synonyms**: Object that rewrites raw license identifiers (e.g. `{"BSD": "BSD-3-Clause"}`) before matching.
- **unlicensed_action**: Action assigned to packages with no detected license. One of `fail` | `warn` | `info` | `ignore`.
- **unknown_action**: Action assigned to packages whose license label does not look like a valid SPDX identifier. Same value set as above.

## Output Artifact: `{pipeline_name}_LICENSE.json`

The report is written to the current working directory with a `metadata` block plus a flat `dependencies` array.

```json
{
"metadata": {
"pipeline_name": "my_service",
"scan_date": "2026-06-12T14:30:00",
"tool": "CDXGEN",
"policy_used": {
"fail": ["AGPL-*", "SSPL-*"],
"warn": ["BUSL-*", "EPL-*", "LGPL-3.0*"],
"synonyms": {},
"unlicensed_action": "ignore",
"unknown_action": "ignore"
},
"summary": {
"total_dependencies": 50,
"ok": 45,
"fail": 1,
"warn": 2,
"unlicensed": 1,
"unknown": 1
}
},
"dependencies": [
{
"name": "lodash",
"version": "4.17.21",
"licenses": ["MIT"],
"policy_applied": "ok",
"policy_reason": "compliant SPDX license",
"policy_pattern_matched": null,
"license_matched": "MIT"
}
]
}
```

### Field reference

#### `metadata`
- **pipeline_name**: Value of the `pipeline_name` DevOps platform variable.
- **scan_date**: ISO-8601 timestamp when the report was assembled.
- **tool**: `"CDXGEN"`.
- **policy_used**: Deep copy of `LICENSE.LICENSE_POLICY` for auditability.
- **summary.total_dependencies**: Number of entries in `dependencies`.
- **summary.{ok,fail,warn,unlicensed,unknown}**: Count per `policy_applied` bucket.

#### `dependencies[]`
- **name / version**: Package identifiers from the CycloneDX SBOM.
- **licenses**: List of normalized license identifiers (after applying `synonyms`).
- **policy_applied**: Bucket — one of `ok`, `fail`, `warn`, `unlicensed`, `unknown`.
- **policy_reason**: Human-readable explanation (e.g. `matches FAIL pattern 'AGPL-*'`).
- **policy_pattern_matched**: Original policy pattern that matched, or `null`.
- **license_matched**: The specific license that triggered the classification.

### Classification rules

For each component in the SBOM:

1. If the component has **no licenses** → bucket `unlicensed`.
2. Otherwise the licenses are normalized through `synonyms` and each is classified:
- Match against `fail` patterns → `fail`.
- Match against `warn` patterns → `warn`.
- Looks like an SPDX id → `ok`.
- Otherwise → `unknown`.
3. **Highest-risk-wins:** if any license on the package matches `fail`, the package is `fail`; if any matches `warn`, it's `warn`. Only when all licenses are compliant is the package `ok`.

## Console Output (Findings)

When run with `--context false` (default), `engine_license` prints a compliance table showing only `fail` and `warn` findings:

| Severity | ID | Description | Where |
|---|---|---|---|
| critical | AGPL-3.0-itext | License 'AGPL-3.0' for package 'itext' (...) | itext:9.2.0 |
| medium | EPL-2.0-junit-bom | License 'EPL-2.0' for package 'junit-bom' (...) | junit-bom:5.10.2 |

Dependencies classified as `ok`, `unlicensed`, or `unknown` do **not** appear in the console findings.

## Structured Context (`--context true`)

When run with `--context true`, the engine additionally prints a JSON block:

```
===== BEGIN CONTEXT OUTPUT =====
{
"license_context": [
{
"name": "itext",
"version": "9.2.0",
"licenses": ["AGPL-3.0"],
"policy_applied": "fail",
"policy_reason": "matches FAIL pattern 'AGPL-*'",
"policy_pattern_matched": "AGPL-*",
"severity": "critical",
"priority": "very critical"
}
]
}
===== END CONTEXT OUTPUT =====
```

Only `fail` and `warn` dependencies appear in the context output.

## Key Components

- `applications/runner_license_scan.py`: Entry point invoked by `engine_core/handle_scan`. Runs the flow and builds `Finding` objects for the compliance table.
- `infrastructure/entry_points/entry_point_tool.py`: Orchestrator: fetch remote config → fresh SBOM → build report.
- `infrastructure/driven_adapters/license_scan/license_scan_manager.py`: Reads the LICENSE.json and provides structured context extraction.
- `infrastructure/helpers/license_policy.py`: Pure helpers — `build_policy_from_remote_config`, `classify_package`, `looks_like_spdx_id`.
- `domain/usecases/build_license_report.py`: `BuildLicenseReport` use case that reads the CycloneDX SBOM, classifies every dependency, and writes the LICENSE.json artifact.

## Example Usage

```sh
devsecops-engine-tools \
--platform_devops local \
--remote_config_source local \
--remote_config_repo example_remote_config_local \
--module engine_license \
--folder_path path/to/project
```

If `--folder_path` is omitted, the current working directory is scanned.

## Configuration Guidelines

- Keep `LICENSE_POLICY` in remote configuration so policy changes are reviewed and auditable; the report echoes it verbatim under `metadata.policy_used`.
- Use specific SPDX identifiers in `fail` / `warn` patterns when possible (e.g. `AGPL-3.0`, `LGPL-3.0*`); use globs sparingly.
- Use `synonyms` to canonicalize ambiguous labels from the SBOM (e.g. `"BSD"` → `"BSD-3-Clause"`).
- Ensure `SBOM_MANAGER.CDXGEN.FETCH_LICENSE` is `true` so the SBOM includes license metadata.
- Choose `unlicensed_action` and `unknown_action` deliberately:
- `ignore` keeps the package in the report but assigns `info` severity (not shown in findings).
- `warn` / `fail` raises the severity for those buckets.
4 changes: 4 additions & 0 deletions example_remote_config_local/engine_core/ConfigTool.json
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,10 @@
"ENABLED": true,
"TOOL": "XRAY|DEPENDENCY_CHECK|TRIVY"
},
"ENGINE_LICENSE": {
"ENABLED": true,
"PRIORITY": "DISCREET"
},
"ENGINE_CODE": {
"ENABLED": true,
"TOOL": "BEARER|KIUWAN",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"LICENSE": {
"LICENSE_POLICY": {
"fail": ["AGPL-*", "SSPL-*"],
"warn": ["BUSL-*", "EPL-*", "LGPL-3.0*"],
"synonyms": {
},
"unlicensed_action": "ignore",
"unknown_action": "ignore",
"severity_mapping": {
"fail": "critical",
"warn": "medium",
"ok": "info",
"ignore": "info"
}
}
},
"THRESHOLD": {
"VULNERABILITY": {},
"COMPLIANCE": {
"Critical": 1
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ def get_inputs_from_cli(args):
"xray",
"dependency_check",
"kiuwan",
"cdxgen",
"all_tools",
],
type=str,
Expand All @@ -132,6 +133,7 @@ def get_inputs_from_cli(args):
"engine_secret",
"engine_dependencies",
"engine_container",
"engine_license",
"engine_risk",
"engine_function",
],
Expand Down Expand Up @@ -266,6 +268,7 @@ def get_inputs_from_cli(args):
"engine_secret": ["trufflehog", "gitleaks", "all_tools"],
"engine_container": ["prisma", "trivy"],
"engine_dependencies": ["xray", "dependency_check", "trivy"],
"engine_license": ["cdxgen"],
"engine_code": ["bearer", "kiuwan"],
"engine_dast": ["nuclei"],
"engine_risk": None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@
from devsecops_engine_tools.engine_sca.engine_dependencies.src.applications.runner_dependencies_scan import (
runner_engine_dependencies,
)
from devsecops_engine_tools.engine_sca.engine_license.src.applications.runner_license_scan import (
runner_engine_license,
)
from devsecops_engine_tools.engine_sca.engine_license.src.infrastructure.driven_adapters.license_scan.license_scan_manager import (
LicenseScanManager,
)
from devsecops_engine_tools.engine_sca.engine_function.src.applications.runner_function_scan import (
runner_engine_function,
)
Expand Down Expand Up @@ -220,6 +226,27 @@ def process(self, dict_args: any, config_tool: any):
config_tool, input_core, dict_args, secret_tool, env, sbom_components
)

self.risk_score_gateway.get_risk_score(findings_list, config_tool, dict_args["module"])
return findings_list, input_core
elif "engine_license" in dict_args["module"]:
findings_list, input_core, _sbom_components = runner_engine_license(
dict_args,
config_tool,
secret_tool,
self.devops_platform_gateway,
self.remote_config_source_gateway,
self.sbom_tool_gateway,
)

self._handle_context_extraction(
dict_args,
"engine_license",
input_core.path_file_results,
config_tool["ENGINE_LICENSE"],
LicenseScanManager(),
config_tool
)

self.risk_score_gateway.get_risk_score(findings_list, config_tool, dict_args["module"])
return findings_list, input_core

Expand Down
Loading
Loading