Generating SBOMs using GitHub Actions and cdxgen
June 10, 2025Introduction
In today’s regulatory and security-conscious landscape, software transparency is no longer optional, especially for organizations dealing with special compliance requirements. One of the key requirements can be, being able to account for every software component shipped within their applications. I recently worked with a customer to strengthen their Software Supply Chain by adopting a Software Bill of Materials (SBOM) for all the software they build and will build. To achieve this, we implemented an automated process for generating an SBOM using GitHub Actions, CycloneDX cdxgen and other tools as part of their CI/CD pipeline, as well as, uploading the generated SBOM to Dependency-Track. In this post, I’ll walk you through what an SBOM is, why it matters, and how we built a practical, scalable solution tailored to the customer’s compliance and security needs.
What is an SBOM and Why It Matters
An SBOM can also be described as an ingredient list of your piece of software. It details all the components, libraries, and dependencies included in a piece of software, and can be used as a base for vulnerability checking. It works similar to how food labels list ingredients for transparency and safety. For our customer, the SBOM became a critical asset in demonstrating compliance with Export Control regulations, which often require detailed disclosures of software origins, licensing, and potential cryptographic content.
Beyond regulatory compliance, an SBOM provides value across the board: it improves software supply chain visibility, supports vulnerability management, and simplifies license compliance. In our case, the focus was clearly on enabling traceability and auditability, ensuring that every component of software being developed can be perceived. This proactive approach not only supports compliance goals, but also lays a strong foundation for secure software delivery practices.
How is an SBOM Structured?
SBOM Formats
SBOMs can be generated in various formats, depending on the tools and environment used. The most widely adopted formats are CycloneDX and SPDX. While both CycloneDX and SPDX are widely adopted SBOM formats, they serve slightly different focuses. SPDX, initiated by the Linux Foundation, was originally designed with license compliance in mind, making it ideal for legal and open-source policy use cases. CycloneDX, on the other hand, was created by the OWASP Foundation and leans more towards security and supply chain risk management, offering a more streamlined structure for vulnerability analysis and integration with DevSecOps workflows.
Depending on the tool, SBOMs can be generated in various formats, including JSON, XML, YAML and protobuf.
SBOM Components
An SBOM typically includes:
- Component Name: The name of the software component or library.
- Version: The version of the component.
- License: License information under which the component is distributed.
- Origin: The source from which the component was obtained (e.g., public repository, private library).
- Dependencies: A list of other components that the component depends on.
- Vulnerabilities: Known vulnerabilities associated with the component, if any.
- Relationships: How components are related to each other, such as parent-child relationships or dependencies.
- Metadata: Additional information such as the author, description, and creation date.
Depending on the use case, the SBOM can also include additional data related to the component, like cryptographic information or information about external APIs the application calls. A full specification for CycloneDX format can be found in the CycloneDX documentation. For SPDX you’ll find specifications and examples in the SPDX documentation.
Which SBOM Format to Choose?
It depends on the use case and the tools you are using. For our customer, the choice of SBOM format was set to CycloneDX, due to its focus on security and the tools they wanted to integrate. Depending on the use case, the choice of SBOM format can vary, i.e. if you host your project on GitHub and prefer getting your SBOM delivered right from it, you need to use SPDX format, as GitHub SBOMs are only available in this format. There are tools available that can convert between formats, but this can lead into additional complexity and potential loss of information.
On Selecting the Right Tool
Choosing the right SBOM generation tool was a balancing act between accuracy, language coverage, ease of integration, and license visibility. No single tool offers 100% coverage, especially when dealing with a mix of public and private libraries, or when multiple programming languages are used within the same codebase. We evaluated several tools including Trivy, Snyk, and cdxgen, comparing their output quantity, as well as, quality, ecosystem support, and how well they fit into our CI/CD pipeline. In the end, we selected cdxgen for its strong multi-language support, native CycloneDX output, and ability to work effectively using Docker with the provided images. Since CycloneDX format was a must-have for the customer, cdxgen was a practical and extensible choice for our audit-focused requirements.
Generating SBOMs with cdxgen and GitHub Actions
The SBOM generation process should be designed to be as automated and seamless as possible to lower the barrier for teams to adopt it. Since GitHub Actions was already in use for the customer’s CI/CD pipelines, we decided to leverage it for SBOM generation, as well.
The goal was to create a straightforward, automated process that would generate an SBOM for each release and then upload it to Dependency-Track.
We divided the process into three main steps:
- SBOM Generation: Using the cdxgen tool to generate the SBOM in CycloneDX format.
- SBOM Enrichment: Using the additional tools to enrich the SBOM, i.e. with additional license information or custom licenses.
- SBOM Upload: Uploading the generated SBOM to Dependency-Track, a platform for managing SBOMs and vulnerabilities.
Step 1: Generating the SBOM
We created a reusable GitHub Action workflow which runs the cdxgen Docker image and upload a generated JSON file to Dependency-Track. Since there are projects in different languages, we added support to choose language specific cdxgen images through optional inputs.
name: Generate and Upload SBOM
on:
workflow_call:
inputs:
cdx_version:
required: false
type: string
default: v11.2.3
description: >
Version tag of the CycloneDX generator to use.
Find a listing of available versions at:
https://github.com/CycloneDX/cdxgen/pkgs/container/cdxgen/versions?filters%5Bversion_type%5D=tagged
cdx_image:
required: false
type: string
default: cdxgen
description: >
Image name of the CycloneDX generator to use.
Helpful for choosing a specific language version (e.g. cdxgen-java17).
Find a listing of available images at: https://github.com/orgs/CycloneDX/packages?repo_name=cdxgen
use_cdxgen_debug_mode:
required: false
type: boolean
default: false
description: >
Enable debug mode for CycloneDX generator
secrets:
# setting some secrets to be used in the workflow
jobs:
generate-sbom-and-upload-to-dependency-track:
runs-on: ubuntu-latest
steps:
# First, we need to set up the environment and install necessary tools like
# checkout the code, set credentials for private registries or hosting providers
- name: Setup environment
- uses: ...
# Since we may access private repositories or registries, we need to authorize to GitHub
- name: Authorize to Github
run: |
git config url.https://x-access-token:${{ secrets.GH_TOKEN }}@github.com/.insteadOf https://github.com/
- name: Generate SBOM
run: |
docker run --rm \
${{ inputs.use_cdxgen_debug_mode && '-e CDXGEN_DEBUG_MODE=debug' || '' }} \
-e FETCH_LICENSE=true \
-v /tmp:/tmp -v $(pwd):/app:rw \
ghcr.io/cyclonedx/${{ inputs.cdx_image }}:${{ inputs.cdx_version }} \
-r /app -o /app/sbom.json
# After generating the SBOM, it is uploaded it to Dependency-Track
- name: Upload SBOM to Dependency-Track
run: |
echo "{\"project\": \"UNIQUE-PROJECT-UUID\",\"bom\": \"$(cat sbom.json | base64 --wrap=0)\"}" > sbom_64.json
curl -X PUT -H "Content-Type: application/json" -H "X-Api-Key: DEPENDENCY-API-KEY" \
-d @sbom_64.json DEPENDENCY-API-URL/api/v1/bom/
Important keys and access credentials should be passed as secrets to the GitHub Action workflow to prevent them from being tracked.
Step 2: Enriching the SBOM
While the cdxgen tool provides a solid foundation for generating SBOMs including license information, in some situations not all license information are provided. Getting no proper license information can result from various factors, such as:
- The component is a private library or a custom-built component.
- The component is a public library, but the license information is not available in the expected format.
- The component is a public library, but does not provide license information at all.
- The generation tool does not support fetching license information for the setup of the specific component or language.
It can be problematic if no license information is available for the component, especially when dealing with compliance and audit requirements. To address this, we implemented an optional enrichment step that allows teams to add custom license information or additional metadata to the SBOM.
The tools we used are snyk’s parlay and cyclonedx-enrich. Through cyclonedx-enrich we were able to add custom license information based on regex patterns in the components package url, the so-called purl in short. A purl is a standardized way to identify software components, including their type, namespace, name, version, qualifiers, and subpath.
When to Generate the SBOM?
We recommend that the SBOM generation should be part of the release process, when ever a new stable version of an application or piece of software is built and released. With the reusable GitHub Action workflow, developer teams can decide when to integrate the SBOM generation into their CI/CD pipeline.
Customer Benefits and Overall Adoption
With the reusable GitHub Action workflow in place, the customer was able to automate the SBOM generation process for their applications. Dev teams could easily integrate the SBOM generation into their existing CI/CD pipelines, ensuring that every release included an accurate SBOM.
To observe the overall adoption of the SBOM generation process, we implemented a dashboard in Grafana that provides insights into the SBOMs generated, their components, and any vulnerabilities detected. The data for the dashboard is collected from the Dependency-Track API.
Lessons Learned
While the integration was largely smooth, we encountered a few things along the way:
Adding full support for every dev team and their applications
Since the customer has multiple development teams, each with their own applications, languages used and dependencies, we had to ensure that the SBOM generation process was flexible enough to accommodate at least most of the setups. However, with all the specifics for different programming languages, libraries and frameworks, it was not possible to cover every single case. To address this, we provided a comprehensive guide and documentation for the teams to follow, including best practices for generating SBOMs in their specific environments.
Team Responsibility
We emphasized the importance of team ownership over the SBOM process. Each dev team is responsible for ensuring that their SBOM is compliant to the requirements. Since our customer has already an established culture of accountability and awareness around software transparency, this was not a big challenge. This culture helped a lot improving the over all process of generating SBOM, since the dev teams could propose improvements and enhancements to the SBOM generation process in the reusable GitHub Action workflow. Additionally, to support teams implementing the SBOM generation process, comprehensive documentation, as well as, dedicated communication channels were set up.
Performance
Generating SBOMs for large project setups can take longer time than expected, especially when dealing with complex dependencies or large codebases. While providing optional usage of language specific cdxgen images, we also suggested to let the SBOM workflow run independently of the main build process, to avoid blocking the CI/CD pipeline.
Conclusion
The implementation of an automated SBOM generation process using GitHub Actions and cdxgen has significantly enhanced our customer’s compliance and security posture. Since it integrated well with the customer’s existing CI/CD and Dependency Track, it provided a practical and easy-to-integrate solution for generating SBOMs.