Generate an SBOM

dfetch report generates a CycloneDX SBOM listing every vendored dependency with its URL, revision, and auto-detected license. Downstream tools can use the SBOM to monitor for known vulnerabilities or enforce a license policy across an organisation.

$ dfetch report -t sbom -o dfetch.cdx.json

Dfetch parses each project’s license at report time and can recognise common license files with high accuracy. For every fetched project the licenses field is always populated:

  • Identified — the SPDX identifier is recorded (e.g. MIT, Apache-2.0), and the base64-encoded license text is embedded in licenses[].text so downstream tooling can verify the text matches the declared identifier.

  • File found, unclassifiable — a license-like file (LICENSE, COPYING, …) was detected but its text could not be matched to a known SPDX identifier with sufficient confidence. The field is set to the SPDX expression NOASSERTION with acknowledgement set to concluded and dfetch:license:noassertion:reason set to UNCLASSIFIABLE_LICENSE_TEXT.

  • No license file present — no license-like file was found. The field is set to the SPDX expression NOASSERTION with acknowledgement set to concluded and dfetch:license:noassertion:reason set to NO_LICENSE_FILE.

This ensures the licenses field is never silently omitted for scanned projects, giving downstream compliance tooling actionable context regardless of the detection outcome. Projects that have never been fetched are not scanned and will not have license assertions in the SBOM output.

Example: A fetched archive with an unclassifiable license file gets NOASSERTION
../features/report-sbom-license.feature
Given an archive "SomeProject.tar.gz" with the files
    | path    |
    | LICENSE |
And the manifest 'dfetch.yaml'
    """
    manifest:
      version: '0.0'

      projects:
        - name: SomeProject
          url: some-remote-server/SomeProject.tar.gz
          vcs: archive
    """
And all projects are updated
When I run "dfetch report -t sbom"
Then the 'report.cdx.json' json file includes
    """
    {
        "components": [
            {
                "name": "SomeProject",
                "licenses": [
                    {
                        "acknowledgement": "concluded",
                        "expression": "NOASSERTION"
                    }
                ],
                "properties": [
                    {
                        "name": "dfetch:license:finding",
                        "value": "License file(s) found (LICENSE) but could not be classified"
                    },
                    {
                        "name": "dfetch:license:noassertion:reason",
                        "value": "UNCLASSIFIABLE_LICENSE_TEXT"
                    },
                    {
                        "name": "dfetch:license:threshold",
                        "value": "0.80"
                    },
                    {
                        "name": "dfetch:license:tool",
                        "value": "<infer-license-version>"
                    }
                ],
                "evidence": {
                    "licenses": [
                        {
                            "acknowledgement": "concluded",
                            "expression": "NOASSERTION"
                        }
                    ]
                }
            }
        ]
    }
    """
Example: A fetched archive with no license file gets NOASSERTION
../features/report-sbom-license.feature
Given an archive "SomeProject.tar.gz" with the files
    | path       |
    | README.md  |
And the manifest 'dfetch.yaml'
    """
    manifest:
      version: '0.0'

      projects:
        - name: SomeProject
          url: some-remote-server/SomeProject.tar.gz
          vcs: archive
    """
And all projects are updated
When I run "dfetch report -t sbom"
Then the 'report.cdx.json' json file includes
    """
    {
        "components": [
            {
                "name": "SomeProject",
                "licenses": [
                    {
                        "acknowledgement": "concluded",
                        "expression": "NOASSERTION"
                    }
                ],
                "properties": [
                    {
                        "name": "dfetch:license:finding",
                        "value": "No license file found in source tree"
                    },
                    {
                        "name": "dfetch:license:noassertion:reason",
                        "value": "NO_LICENSE_FILE"
                    },
                    {
                        "name": "dfetch:license:threshold",
                        "value": "0.80"
                    },
                    {
                        "name": "dfetch:license:tool",
                        "value": "<infer-license-version>"
                    }
                ],
                "evidence": {
                    "licenses": [
                        {
                            "acknowledgement": "concluded",
                            "expression": "NOASSERTION"
                        }
                    ]
                }
            }
        ]
    }
    """

License detection auditability

For every scanned component dfetch records properties that let auditors reproduce or re-evaluate results without re-running dfetch:

dfetch:license:<license-label>:confidence

The probability score (0-1) returned by infer-license for each successfully identified license. The label is typically the SPDX ID, but may fall back to another detected license label when SPDX is unavailable.

dfetch:license:threshold

The minimum confidence required to accept an inference (0.80 by default). If the threshold changes, stored scores can be compared against the new value without re-fetching.

dfetch:license:tool

The infer-license library version used during the scan. Different library versions may classify the same text differently; recording the version enables reproducible re-evaluation.

For components with NOASSERTION licenses two additional properties provide machine-readable context:

dfetch:license:noassertion:reason

NO_LICENSE_FILE or UNCLASSIFIABLE_LICENSE_TEXT, indicating why the license could not be asserted.

dfetch:license:finding

A human-readable description of the detection outcome — for example, “License file(s) found (LICENSE) but could not be classified” or “No license file found in source tree”. Useful for quick triage in dashboards that surface custom component properties.

Archive dependencies (tar.gz, zip, …) are recorded with a distribution external reference. When an integrity.hash: field is set in the manifest, the SBOM includes a SHA-256 component hash for supply-chain integrity verification.

Example: A fetched project generates a json sbom
../features/report-sbom.feature
Given the manifest 'dfetch.yaml'
    """
    manifest:
      version: '0.0'

      projects:
        - name: cpputest
          url: https://github.com/cpputest/cpputest
          tag: v3.4
          src: 'include/CppUTest'
    """
And all projects are updated
When I run "dfetch report -t sbom"
Then the 'report.cdx.json' json file includes
    """
    {
        "components": [
            {
                "bom-ref": "cpputest-v3.4",
                "evidence": {
                    "identity": [
                        {
                            "concludedValue": "cpputest",
                            "field": "name",
                            "methods": [
                                {
                                    "confidence": 0.4,
                                    "technique": "manifest-analysis",
                                    "value": "Name as used for project in dfetch.yaml"
                                }
                            ]
                        },
                        {
                            "concludedValue": "pkg:github/cpputest/cpputest@v3.4#include/CppUTest",
                            "field": "purl",
                            "methods": [
                                {
                                    "confidence": 0.4,
                                    "technique": "manifest-analysis",
                                    "value": "Determined from https://github.com/cpputest/cpputest as used for the project cpputest in dfetch.yaml"
                                }
                            ]
                        },
                        {
                            "concludedValue": "v3.4",
                            "field": "version",
                            "methods": [
                                {
                                    "confidence": 0.4,
                                    "technique": "manifest-analysis",
                                    "value": "Version as used for project in dfetch.yaml"
                                }
                            ]
                        }
                    ],
                    "licenses": [
                        {
                            "license": {
                                "id": "BSD-3-Clause"
                            }
                        }
                    ],
                    "occurrences": [
                        {
                            "line": 5,
                            "location": "dfetch.yaml",
                            "offset": 13
                        }
                    ]
                },
                "externalReferences": [
                    {
                        "type": "vcs",
                        "url": "https://github.com/cpputest/cpputest"
                    }
                ],
                "group": "cpputest",
                "licenses": [
                    {
                        "license": {
                            "id": "BSD-3-Clause"
                        }
                    }
                ],
                "name": "cpputest",
                "purl": "pkg:github/cpputest/cpputest@v3.4#include/CppUTest",
                "properties": [
                    {
                        "name": "dfetch:license:BSD-3-Clause:confidence"
                    },
                    {
                        "name": "dfetch:license:threshold",
                        "value": "0.80"
                    },
                    {
                        "name": "dfetch:license:tool",
                        "value": "<infer-license-version>"
                    }
                ],
                "type": "library",
                "version": "v3.4"
            }
        ],
        "dependencies": [
            {
                "ref": "cpputest-v3.4"
            }
        ],
        "metadata": {
            "timestamp": "2025-10-10T18:28:32.074803+00:00",
            "tools": {
                "components": [
                    {
                        "bom-ref": "dfetch-0.13.0",
                        "externalReferences": [
                            {
                                "type": "build-system",
                                "url": "https://github.com/dfetch-org/dfetch/actions"
                            },
                            {
                                "type": "distribution",
                                "url": "https://pypi.org/project/dfetch/"
                            },
                            {
                                "type": "documentation",
                                "url": "https://dfetch.readthedocs.io/"
                            },
                            {
                                "type": "issue-tracker",
                                "url": "https://github.com/dfetch-org/dfetch/issues"
                            },
                            {
                                "type": "license",
                                "url": "https://github.com/dfetch-org/dfetch/blob/main/LICENSE"
                            },
                            {
                                "type": "release-notes",
                                "url": "https://github.com/dfetch-org/dfetch/blob/main/CHANGELOG.rst"
                            },
                            {
                                "type": "vcs",
                                "url": "https://github.com/dfetch-org/dfetch"
                            },
                            {
                                "type": "website",
                                "url": "https://dfetch-org.github.io/"
                            }
                        ],
                        "licenses": [
                            {
                                "license": {
                                    "acknowledgement": "declared",
                                    "id": "MIT"
                                }
                            }
                        ],
                        "name": "dfetch",
                        "supplier": {
                            "name": "dfetch-org"
                        },
                        "type": "application",
                        "version": "0.13.0"
                    },
                    {
                        "description": "Python library for CycloneDX",
                        "externalReferences": [
                            {
                                "type": "build-system",
                                "url": "https://github.com/CycloneDX/cyclonedx-python-lib/actions"
                            },
                            {
                                "type": "distribution",
                                "url": "https://pypi.org/project/cyclonedx-python-lib/"
                            },
                            {
                                "type": "documentation",
                                "url": "https://cyclonedx-python-library.readthedocs.io/"
                            },
                            {
                                "type": "issue-tracker",
                                "url": "https://github.com/CycloneDX/cyclonedx-python-lib/issues"
                            },
                            {
                                "type": "license",
                                "url": "https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/LICENSE"
                            },
                            {
                                "type": "release-notes",
                                "url": "https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md"
                            },
                            {
                                "type": "vcs",
                                "url": "https://github.com/CycloneDX/cyclonedx-python-lib"
                            },
                            {
                                "type": "website",
                                "url": "https://github.com/CycloneDX/cyclonedx-python-lib/#readme"
                            }
                        ],
                        "group": "CycloneDX",
                        "licenses": [
                            {
                                "license": {
                                    "acknowledgement": "declared",
                                    "id": "Apache-2.0"
                                }
                            }
                        ],
                        "name": "cyclonedx-python-lib",
                        "type": "library",
                        "version": "11.7.0"
                    }
                ]
            }
        },
        "serialNumber": "urn:uuid:7621038e-3047-4862-99e7-d637ee9458a9",
        "version": 1,
        "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json",
        "bomFormat": "CycloneDX",
        "specVersion": "1.6"
    }
    """
Example: A fetched archive without a hash generates a json sbom
../features/report-sbom-archive.feature
Given an archive "SomeProject.tar.gz"
And the manifest 'dfetch.yaml'
    """
    manifest:
      version: '0.0'

      projects:
        - name: SomeProject
          url: some-remote-server/SomeProject.tar.gz
          vcs: archive
    """
And all projects are updated
When I run "dfetch report -t sbom"
Then the 'report.cdx.json' json file includes
    """
    {
        "components": [
            {
                "name": "SomeProject",
                "type": "library",
                "externalReferences": [
                    {
                        "type": "distribution",
                        "url": "<archive-url>"
                    }
                ]
            }
        ]
    }
    """
Example: A fetched archive with sha256 hash generates a json sbom with hash
../features/report-sbom-archive.feature
Given an archive "SomeProject.tar.gz"
And the manifest 'dfetch.yaml'
    """
    manifest:
      version: '0.0'

      projects:
        - name: SomeProject
          url: some-remote-server/SomeProject.tar.gz
          vcs: archive
          integrity:
            hash: sha256:<archive-sha256>
    """
And all projects are updated
When I run "dfetch report -t sbom"
Then the 'report.cdx.json' json file includes
    """
    {
        "components": [
            {
                "name": "SomeProject",
                "version": "sha256:<archive-sha256>",
                "type": "library",
                "hashes": [
                    {
                        "alg": "SHA-256",
                        "content": "<archive-sha256>"
                    }
                ],
                "externalReferences": [
                    {
                        "type": "distribution",
                        "url": "<archive-url>"
                    }
                ]
            }
        ]
    }
    """

Viewing the SBOM in DependencyTrack

DependencyTrack is a popular open-source SBOM analysis platform that ingests CycloneDX SBOMs generated by dfetch.

NOASSERTION is the SPDX value for a component whose license cannot be determined. Dfetch uses it when no license file is found or when the file text cannot be matched to a known SPDX identifier. See the SPDX specification for the full definition.

When viewing a component with a NOASSERTION license in DependencyTrack:

  • The license column shows NOASSERTION.

  • The properties panel displays the dfetch license detection metadata (dfetch:license:noassertion:reason, dfetch:license:finding, etc.).

  • The license detail view is empty — DependencyTrack has no detail to show for a non-specific SPDX expression. The raw CycloneDX payload carries acknowledgement and the dfetch:license:finding property for human-readable context.

Properties view showing dfetch license detection metadata Component overview displaying NOASSERTION in the license column License detail view, which remains empty for NOASSERTION components

GitLab

Upload the SBOM as a CycloneDX artifact so GitLab surfaces it in the dependency scanning dashboard. See GitLab dependency scanning for details.

dfetch:
  image: "python:3.13"
  script:
    - pip install dfetch
    - dfetch report -t sbom -o dfetch.cdx.json
  artifacts:
    reports:
      cyclonedx:
        - dfetch.cdx.json

GitHub Actions

Generate and upload the SBOM as a workflow artifact. See GitHub dependency submission for details.

jobs:
  SBOM-generation:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-python@v6
        with:
          python-version: '3.13'
      - name: Generate SBOM
        run: pip install dfetch && dfetch report -t sbom -o dfetch.cdx.json
      - uses: actions/upload-artifact@v4
        with:
          name: sbom
          path: dfetch.cdx.json