A GitHub Action that generates a CycloneDX SBOM listing every GitHub Action used in your workflow, with each ref resolved to the exact commit SHA the runner would execute.
At the end of your workflow, this action reads the workflow file, parses every uses: entry, and calls the GitHub API to resolve tags and branch names to their actual commit SHAs - matching the resolution order used by the runner (branches take priority over tags when both exist for the same name contrary to documented behaviour). The result is written as a CycloneDX JSON file.
Each component in the SBOM includes:
- The resolved commit SHA as the version
- A PURL (
pkg:github/owner/repo@<sha>) - The original ref as written in the workflow (
cdx:github:action:ref) - A
cdx:github:action:suspicious-ref: trueproperty when the ref contains invisible or non-printable Unicode characters (a known supply-chain attack vector)
Add this as the last step in any job. The action auto-detects the current workflow file via GITHUB_WORKFLOW_REF.
steps:
- uses: actions/checkout@v4
- name: Run tests
run: npm test
- name: Generate Actions SBOM
uses: coderpatros/action-sbom@v1steps:
- uses: actions/checkout@v4
- name: Run tests
run: npm test
- name: Generate Actions SBOM
id: sbom
uses: coderpatros/action-sbom@v1
with:
output-file: actions-sbom.json
- name: Upload SBOM artifact
uses: actions/upload-artifact@v4
with:
name: actions-sbom
path: ${{ steps.sbom.outputs.sbom-path }}| Input | Required | Default | Description |
|---|---|---|---|
output-file |
No | actions-sbom.json |
Path to write the SBOM JSON file |
github-token |
No | ${{ github.token }} |
Token used to resolve action refs via the GitHub API |
workflow-file |
No | (auto-detected) | Workflow file to scan, relative to the repo root. Auto-detected from GITHUB_WORKFLOW_REF when omitted. |
| Output | Description |
|---|---|
sbom-path |
Absolute path to the generated SBOM file |
component-count |
Number of action components included in the SBOM |
A standalone CLI script is included for generating an SBOM from any workflow URL without needing to run a GitHub Actions workflow.
# Print SBOM to stdout
python3 scripts/sbom.py https://github.com/owner/repo/blob/main/.github/workflows/ci.yml
# Write to a file
python3 scripts/sbom.py https://github.com/owner/repo/blob/main/.github/workflows/ci.yml \
-o sbom.json
# Pass a token to avoid rate limiting on public repos or to access private repos
python3 scripts/sbom.py https://github.com/owner/repo/blob/main/.github/workflows/ci.yml \
--token "$GITHUB_TOKEN" \
-o sbom.jsonBoth GitHub blob URLs (github.com/.../blob/...) and raw content URLs (raw.githubusercontent.com/...) are accepted.
scripts/repo_sboms.py generates one SBOM per workflow file for every workflow found in a repository's .github/workflows/ directory, without needing to clone the repo.
# Write one SBOM per workflow into the current directory
python3 scripts/repo_sboms.py owner/repo
# Write SBOMs into a specific output directory
python3 scripts/repo_sboms.py owner/repo -o sboms/
# Scan a specific branch, tag, or SHA
python3 scripts/repo_sboms.py owner/repo --ref main -o sboms/
# A full GitHub URL is also accepted
python3 scripts/repo_sboms.py https://github.com/owner/repo -o sboms/
# Pass a token to avoid rate limiting or to access private repos
python3 scripts/repo_sboms.py owner/repo --token "$GITHUB_TOKEN" -o sboms/Each workflow file ci.yml produces a corresponding ci.json SBOM in the output directory. The workflow_name field in each SBOM's metadata identifies the source file as owner/repo/.github/workflows/ci.yml.
scripts/org_sboms.py extends repo_sboms.py to cover an entire GitHub organisation. It discovers every repository in the org and generates one SBOM per workflow file for each repo, without needing to clone anything.
# Scan all repos in an org, write SBOMs under ./sboms/<owner>/<repo>/
python3 scripts/org_sboms.py my-org -o sboms/
# Skip archived and forked repos
python3 scripts/org_sboms.py my-org -o sboms/ --skip-archived --skip-forks
# Only scan public repos
python3 scripts/org_sboms.py my-org -o sboms/ --repo-type public
# Pass a token to avoid rate limiting or to access private repos
python3 scripts/org_sboms.py my-org --token "$GITHUB_TOKEN" -o sboms/Output is written under <output-dir>/<owner>/<repo>/, with one JSON file per workflow — the same layout repo_sboms.py uses for a single repo. For example, .github/workflows/ci.yml in my-org/my-repo becomes sboms/my-org/my-repo/ci.json.
| Option | Description |
|---|---|
-o, --output DIR |
Root directory to write SBOMs into (default: current directory) |
-t, --token TOKEN |
GitHub token (default: $GITHUB_TOKEN) |
--skip-archived |
Exclude archived repositories |
--skip-forks |
Exclude forked repositories |
--repo-type TYPE |
Filter by type: all, public, private, forks, sources, member (default: all) |
Repos that cannot be scanned (for example because they have no .github/workflows/ directory) are skipped with a warning; the script continues scanning the remaining repos and prints a summary when done.
Invisible Unicode characters in refs - the action detects refs that contain invisible or non-printable Unicode characters (for example a zero-width space appended to what looks like a SHA). These refs are resolved as-is against the GitHub API, because that is what the runner does - but the affected component is flagged with cdx:github:action:suspicious-ref: true in the SBOM and a warning annotation is emitted in the workflow log. An invisible-char ref can make a branch reference look visually identical to a pinned SHA, allowing an attacker-controlled branch to be silently executed.
Branch/tag priority - when a branch and a tag share the same name, the runner resolves the branch. This action matches that behaviour.