dependency-track
dependency-track copied to clipboard
Add support for multiple licenses
Components are often released under multiple licenses. SPDX license expressions provide this, and other capabilities.
https://github.com/CycloneDX/specification/issues/1
This could be as simple as supporting SPDX expressions rather than changing table license/component relationships from 1-M to N-M.
Investigate use of:
- https://github.com/aschet/spdx-license-expression-tools
- https://github.com/aschet/spdx-license-compat
Is there anything I can do here as a workaround when using the cyclonedx-bom
(python) tool?
I don't have any use-case for multiple licenses it's just single licenses showing up in this expression
format such as
{
"type": "library",
"bom-ref": "f98ac10c-6596-417e-b518-3a98cef0c2da",
"author": "A lot of people",
"name": "pyflakes",
"version": "2.3.1",
"licenses": [
{
"expression": "MIT"
}
],
"purl": "pkg:pypi/[email protected]"
}
you could try to fork the python library and change the code to emit either spdx license id or unresolved license names. If you didn't want to do that, you could create a transformation utility that takes the existing format and does the same conversion. Other than that, not sure what else can be done.
trivy also put it in expression trivy image --format cyclonedx --output result.json alpine:3.15
"bom-ref": "pkg:apk/alpine/[email protected]?distro=3.15.3",
"type": "library",
"name": "alpine-keys",
"version": "2.4-r1",
"licenses": [
{
"expression": "MIT"
}
],
it seems to always ever be one lincense in there: https://github.com/aquasecurity/trivy/blob/7a148089ece78746f363d5358048d9a74599cedf/pkg/report/cyclonedx/cyclonedx.go#L291-L295
If it helps, I wrote a short python script for the conversion - https://gist.github.com/mariuskimmina/4d78f4d776b5ac5cecf408f08919e156 This probably fails in a lot of cases (like when there are acctually multiple licenses) but it worked for my simple case - getting the single licenses to display in dependencytrack
The conversion of the SPDX expression to the SPDX name property (using a script) does show the license in DT but a lot of the licenses are not detected by DT which causes the Policy Management rules to fail.
Is it planned to support the expressions format for licenses ?
"licenses": [
{
"expression": "MIT"
}
]
Yes @kishankarun. SPDX expression support is planned as part of this ticket.
@stevespringett Do you have any update/estimate about when this ticket will be implemented?
@jlongo-encora No ETA at this point. It's not targeted towards any milestone yet. I just added a help wanted tag to the ticket.
:disappointed: This feature is much awaited by our dev teams. Especially in combination with https://github.com/DependencyTrack/dependency-track/issues/1732 it would be a huge improvement regarding OSS license clearing.
Sadly I can't support the implementation, because this is beyond my developer skills. :sweat_smile:
For now, as a workaround, I'll restructure the CycloneDX files before uploading.
Approach:
If a component has more than one detected license, I duplicate it into separate components. One per license.
The first one keeps the original name, cpe and purl.
And all the clones get the cpe and purl removed. The name is modified to <component-name> (additional license)
.
This seems necessary, because otherwise DependencyTrack would remove components with identical purl / cpe.
This gives me the ability to
- show all detected licenses and apply policies to them
- show CVEs only once per component
The "downside" of this is, that the total number of components is slightly increased. But that's ok for our use case.
CycloneDX (modified):
"components" : [
{
"name" : "apk-tools",
"version" : "2.12.1-r0",
"description" : "Alpine Package Keeper - package manager for alpine",
"licenses" : [
{
"license" : {
"id" : "GPL-2.0-only"
}
}
],
"cpe" : "cpe:2.3:a:apk-tools:apk-tools:2.12.1-r0:*:*:*:*:*:*:*",
"purl" : "pkg:alpine/[email protected]?arch=x86_64&distro=alpine-3.13.2&upstream=apk-tools",
"externalReferences" : [
{
"type" : "distribution",
"url" : "https://gitlab.alpinelinux.org/alpine/apk-tools"
}
],
"type" : "library"
},
{
"name" : "apk-tools (additional license)",
"group": "oss.clearing",
"version" : "2.12.1-r0",
"description" : "Alpine Package Keeper - package manager for alpine",
"licenses" : [
{
"license" : {
"id" : "Apache-2.0"
}
}
],
"externalReferences" : [
{
"type" : "distribution",
"url" : "https://gitlab.alpinelinux.org/alpine/apk-tools"
}
],
"type" : "library"
}
]
}
Maybe this approach is also a suitable workaround for other users. :smiley:
I'm having the same issue. It is impossible to manage licenses of Rust projects because many of the projects are dual-licensed, e.g. MIT or Apache-2.0
. This format is fine by SPDX, but unfortunately, there is no good way to deal with this with Dependency Track.
Regarding the above-mentioned workaround, I created a similar script to do the conversion. What is weird is that even though all the components in my case ended up with uniform licenses (see XML below), only the last component had the license showing up on the UI. (see attached screenshot) I'm not entirely sure why <licenses><license><id>MIT</id></license></licenses>
is OK and processed fine for the last component, but not for the others. Is this a bug, or am I missing something?

<bom xmlns="http://cyclonedx.org/schema/bom/1.3" serialNumber="urn:uuid:a1a3a06f-0863-462f-9f98-cdcb1453460a" version="1">
<metadata>
<timestamp>2022-10-26T14:07:05.411453000Z</timestamp>
<tools>
<tool>
<vendor>CycloneDX</vendor>
<name>cargo-cyclonedx</name>
<version>0.3.7</version>
</tool>
</tools>
<component type="application" bom-ref="pkg:cargo/[email protected]">
<name>dtrack</name>
<version>0.1.0</version>
<scope>required</scope>
<licenses>
<expression>MIT</expression>
</licenses>
<purl>pkg:cargo/[email protected]</purl>
</component>
</metadata>
<components>
<component type="library" bom-ref="pkg:cargo/[email protected]">
<name>argparse</name>
<version>0.2.2</version>
<description>Powerful command-line argument parsing library</description>
<scope>required</scope>
<licenses><license><id>MIT</id></license></licenses>
<purl>pkg:cargo/[email protected]</purl>
<externalReferences>
<reference type="website">
<url>http://github.com/tailhook/rust-argparse</url>
</reference>
</externalReferences>
</component>
<component type="library" bom-ref="pkg:cargo/[email protected]">
<name>base64</name>
<version>0.13.1</version>
<description>encodes and decodes base64 as bytes or utf8</description>
<scope>required</scope>
<licenses><license><id>MIT</id></license></licenses>
<purl>pkg:cargo/[email protected]</purl>
<externalReferences>
<reference type="documentation">
<url>https://docs.rs/base64</url>
</reference>
<reference type="vcs">
<url>https://github.com/marshallpierce/rust-base64</url>
</reference>
</externalReferences>
</component>
<component type="library" bom-ref="pkg:cargo/[email protected]">
<name>regex</name>
<version>1.6.0</version>
<description>An implementation of regular expressions for Rust. This implementation uses finite automata and guarantees linear time matching on all inputs. </description>
<scope>required</scope>
<licenses><license><id>MIT</id></license></licenses>
<purl>pkg:cargo/[email protected]</purl>
<externalReferences>
<reference type="documentation">
<url>https://docs.rs/regex</url>
</reference>
<reference type="website">
<url>https://github.com/rust-lang/regex</url>
</reference>
<reference type="vcs">
<url>https://github.com/rust-lang/regex</url>
</reference>
</externalReferences>
</component>
<component type="library" bom-ref="pkg:cargo/[email protected]">
<name>reqwest</name>
<version>0.11.12</version>
<description>higher level HTTP client library</description>
<scope>required</scope>
<licenses><license><id>MIT</id></license></licenses>
<purl>pkg:cargo/[email protected]</purl>
<externalReferences>
<reference type="documentation">
<url>https://docs.rs/reqwest</url>
</reference>
<reference type="vcs">
<url>https://github.com/seanmonstar/reqwest</url>
</reference>
</externalReferences>
</component>
<component type="library" bom-ref="pkg:cargo/[email protected]">
<name>serde</name>
<version>1.0.147</version>
<description>A generic serialization/deserialization framework</description>
<scope>required</scope>
<licenses><license><id>MIT</id></license></licenses>
<purl>pkg:cargo/[email protected]</purl>
<externalReferences>
<reference type="documentation">
<url>https://docs.serde.rs/serde/</url>
</reference>
<reference type="website">
<url>https://serde.rs</url>
</reference>
<reference type="vcs">
<url>https://github.com/serde-rs/serde</url>
</reference>
</externalReferences>
</component>
<component type="library" bom-ref="pkg:cargo/[email protected]">
<name>serde_json</name>
<version>1.0.87</version>
<description>A JSON serialization file format</description>
<scope>required</scope>
<licenses><license><id>MIT</id></license></licenses>
<purl>pkg:cargo/[email protected]</purl>
<externalReferences>
<reference type="documentation">
<url>https://docs.serde.rs/serde_json/</url>
</reference>
<reference type="vcs">
<url>https://github.com/serde-rs/json</url>
</reference>
</externalReferences>
</component>
<component type="library" bom-ref="pkg:cargo/[email protected]">
<name>url</name>
<version>2.3.1</version>
<description>URL library for Rust, based on the WHATWG URL Standard</description>
<scope>required</scope>
<licenses><license><id>MIT</id></license></licenses>
<purl>pkg:cargo/[email protected]</purl>
<externalReferences>
<reference type="documentation">
<url>https://docs.rs/url</url>
</reference>
<reference type="vcs">
<url>https://github.com/servo/rust-url</url>
</reference>
</externalReferences>
</component>
</components>
</bom>
Also looking forward to this. Recently integrated trivy as a scanning solution and had to find out the hard way after debugging for a while that it is a problem with the format.
This issue bit me also right now. Would be wonderfull if there were some way to circumvent this.
@ataraxus Which issue specifically? DT v4.9 added initial support for SPDX expressions. They are now ingested from uploaded SBOMs and can be used in policies (see https://docs.dependencytrack.org/usage/policy-compliance/#license-violation).
@nscuro thanks for pointing this out! I just confirmed we have 4.8 installed...
I have the issue, that DT doesnt detect any license when cyclone produces this:
"licenses": [
{
"expression": "MIT OR Apache-2.0"
}
],
In that case, give v4.9 a try. Without question we still have room for improvement, hence this ticket not being closed yet.
But expressions are now taken correctly and displayed in the UI.
Hello all - a question based on the 4.9.0 release.
I have an npm component: type-fest 0.20.2 that has a SPDX license expression on the public registry.
I was expecting that SPDX expression to appear in the frontend under the SPDX expression, since an expression was returned from the backend:
Component DTO (GET /v1/component/{id}):
{
"name": "type-fest",
"version": "0.20.2",
"classifier": "LIBRARY",
"sha512": "35ef9e138af4fe25a7a40c43f39db3dc0f8dd01b7944dfff36327045dd95147126af2c317f9bec66587847a962c65e81fb0cfff1dfa669348090dd452242372d",
"purl": "pkg:npm/[email protected]",
"purlCoordinates": "pkg:npm/[email protected]",
"license": "(MIT OR CC0-1.0)",
"project": {
"name": "test",
"version": "3",
"classifier": "APPLICATION",
"uuid": "8c8288e2-cd99-49d0-a8bb-1260a262a26c",
"lastBomImport": 1698332194744,
"lastBomImportFormat": "CycloneDX 1.4",
"lastInheritedRiskScore": 6,
"active": true
},
"lastInheritedRiskScore": 0,
"uuid": "6c55ba56-4265-4011-937b-1fde60960e89",
"usedBy": 0,
"expandDependencyGraph": false,
"isInternal": false
}
When I went to the component in the frontend, the SPDX expression was blank:
Is this the expected behavior?
Additionally, after reading and following the policy recommendation, I wrote a test policy to disallow all licenses outside of a certain license group - in this case, the permissive license group.
The permissive license group contains the MIT License
.
Based on the SPDX expression of my test component, type-fest
, which has the following license SPDX expression: (MIT OR CC0-1.0)
, I would have expected my policy to not apply to this component. However, the policy was triggered.
(GET /v1/violation/project/{id}):
{
"type": "LICENSE",
"project": {
"name": "test",
"version": "3",
"classifier": "APPLICATION",
"uuid": "8c8288e2-cd99-49d0-a8bb-1260a262a26c",
"properties": [],
"tags": [],
"lastBomImport": 1698332194744,
"lastBomImportFormat": "CycloneDX 1.4",
"lastInheritedRiskScore": 6,
"active": true
},
"component": {
"name": "type-fest",
"version": "0.20.2",
"classifier": "LIBRARY",
"sha512": "35ef9e138af4fe25a7a40c43f39db3dc0f8dd01b7944dfff36327045dd95147126af2c317f9bec66587847a962c65e81fb0cfff1dfa669348090dd452242372d",
"purl": "pkg:npm/[email protected]",
"purlCoordinates": "pkg:npm/[email protected]",
"license": "(MIT OR CC0-1.0)",
"project": {
"name": "test",
"version": "3",
"classifier": "APPLICATION",
"uuid": "8c8288e2-cd99-49d0-a8bb-1260a262a26c",
"properties": [],
"tags": [],
"lastBomImport": 1698332194744,
"lastBomImportFormat": "CycloneDX 1.4",
"lastInheritedRiskScore": 6,
"active": true
},
"lastInheritedRiskScore": 0,
"uuid": "6c55ba56-4265-4011-937b-1fde60960e89",
"usedBy": 0,
"expandDependencyGraph": false,
"isInternal": false
},
"policyCondition": {
"policy": {
"name": "disallow non-permissive",
"operator": "ANY",
"violationState": "FAIL",
"uuid": "7f6a875f-9341-4389-89a9-259fecdd5a1a",
"includeChildren": false,
"global": true
},
"operator": "IS_NOT",
"subject": "LICENSE_GROUP",
"value": "55a6d79e-304b-4cba-a3f9-5bef3aaf3988",
"uuid": "bd26187b-f643-4127-82e3-3d69f6b30a1a"
},
"timestamp": 1698332196090,
"uuid": "2267f550-9a57-452c-8080-b6c864fbd5e8"
}
Is this the expected behavior - meaning I'm misunderstanding how policies are evaluated against SPDX expressions?
@ansonallard For the SPDX expression feature to work, the expression must be present in the licenseExpression
field. In the API response you shared, it's in the license
field. Did you re-import your BOM after upgrading to 4.9.0?
Additionally, how does the SBOM look that you're uploading? The expression should be in the component.licenses[].expression
field.
The BOM upload was fresh after upgrading to 4.9.0.
It looks like my BOM is incorrect, as this is the component that was pushed to dependency track (CycloneDX 1.4):
{
"group": "",
"name": "type-fest",
"version": "0.20.2",
"hashes": [
{
"alg": "SHA-512",
"content": "35ef9e138af4fe25a7a40c43f39db3dc0f8dd01b7944dfff36327045dd95147126af2c317f9bec66587847a962c65e81fb0cfff1dfa669348090dd452242372d"
}
],
"licenses": [
{
"license": {
"name": "(MIT OR CC0-1.0)"
}
}
],
"purl": "pkg:npm/[email protected]",
"type": "library",
"bom-ref": "pkg:npm/[email protected]",
"properties": [
{
"name": "SrcFile",
"value": "/app/package-lock.json"
}
]
},
May I ask what tool you used to generate the SBOM?
I used cdxgen.
After reviewing the CycloneDX 1.4 spec, it seems this tool does not generate the correct SBOM output given a SPDX expression, as defined here.
After uploading a BOM file with the correct "expression" key/value pair in the license list, I was able to verify that dependency track correctly handles the SPDX expression. I'll raise the issue to the maintainer of my SBOM generation tool. Thank you @nscuro for your insights.
Hi @nscuro, Does Dependency Track consider license Expressions during policy evaluation ?
I uploaded a SBOM which contains component with multiple licenses by using SPDX expression, the SPDX expression is shown in the GUI but the policy evaluation doesn't take into account the licenses i put in the license expression.
Here's the component information in the SBOM
<component type="library"> <name>packageName</name> <version>1.1.1</version> <licenses> <expression>Apache-2.0 AND MIT</expression> </licenses> <purl>pkg:generic/[email protected]</purl> </component>
I create License policy and added the Apache-2.0 to the forbidden licenses Group to test if it's taken into account but i don't see any license violation for this component.