Actions Bill of Materials — map your GitHub Actions supply chain
SBOMs exist for your application dependencies. ABOMs should exist for your CI/CD pipelines.
abom recursively resolves every GitHub Action in your workflows — including actions nested inside composite actions, reusable workflows, and actions that silently embed tools like Trivy in their own code — and builds a complete dependency tree.
$ abom scan . --check
WORKFLOW STEP ACTION REF STATUS
.github/workflows/ci.yml Checkout v4 actions/checkout v4 tag
.github/workflows/ci.yml Setup Node.js actions/setup-node v4 tag
.github/workflows/ci.yml Set up Docker Buildx docker/setup-buildx-action v3 tag
.github/workflows/ci.yml Scan for vulnerabilities crazy-max/ghaction-container-scan v3 COMPROMISED (ABOM-2026-001 (detected via action inputs))
Compromised actions found: 1 (1 direct, 0 transitive)
grep can't find this. The workflow never mentions
trivy-action. Butcrazy-max/ghaction-container-scandownloads and runs Trivy internally.abomdetects it by analyzing the action's metadata.
The Trivy supply chain compromise (CVE-2026-33634) exposed a blind spot: organizations grepped their workflows for trivy-action and found nothing — while compromised code ran in their pipelines through transitive and embedded dependencies.
Every post-incident guide from CrowdStrike, Wiz, Snyk, and Microsoft tells you to grep your workflows. None of them address transitive action dependencies.
abom was built to close that gap.
We hope the Trivy project recovers quickly — this tool exists to address a gap in incident response, not to pile on. Supply chain attacks can happen to any project.
- Recursive resolution — follows composite actions and reusable workflows through the full dependency chain
- Tool wrapper detection — identifies actions that embed known tools (Trivy, Grype, Snyk, etc.) by analyzing
action.ymlinputs and descriptions - Remote scanning — scan any public GitHub repo without cloning:
abom scan github.com/org/repo - Advisory database — built-in + auto-updated database of known-compromised actions
- Standard BOM formats — output as CycloneDX 1.5 or SPDX 2.3 for integration with Dependency-Track, Grype, and other tooling
- SHA verification — optionally verify that pinned SHAs are actually reachable from the upstream repo, catching fork-sourced and force-pushed-away commits (
--verify-shas) - Ref resolution — optionally resolve tag and branch refs to the commit SHA they point to at BOM-generation time, turning a mutable-tag BOM into a stable evidentiary record (
--resolve-refs) - CI gate — exits non-zero when compromised actions are found or (with
--fail-on-warnings) when any advisory warning is emitted - Fast — caches resolved actions locally and fetches action content directly from
raw.githubusercontent.comto avoid API rate limits
Homebrew:
brew install JulietSecurity/tap/abomGo:
go install github.com/julietsecurity/abom@latestBinary releases:
Download prebuilt binaries for Linux, macOS, and Windows from GitHub Releases.
Scan a repository and see every action in your supply chain:
# Local repo
abom scan .
# Remote repo — no clone needed
abom scan github.com/your-org/your-repoCheck for known-compromised actions:
abom scan . --checkGenerate standard BOM formats:
# CycloneDX 1.5 — for Dependency-Track, Grype, etc.
abom scan . -o cyclonedx-json
# SPDX 2.3 — for compliance tooling
abom scan . -o spdx-json
# Native JSON — full dependency tree with metadata
abom scan . -o jsonUse as a CI gate:
- name: Check Actions supply chain
run: abom scan . --checkBlock on fork-sourced SHA pins as well:
- name: Check Actions supply chain
run: abom scan . --check --verify-shas --fail-on-warnings
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}Pinning an action to a SHA (e.g. actions/checkout@a1b2c3...) is the recommended defense against tag-swap attacks. But GitHub's object store is shared across a repo and its forks — so a SHA that exists only on a fork (or was force-pushed out of the upstream's history) will still resolve successfully when a workflow runs. The pin protects you from tag mutation, but not from a commit that was never in the upstream's ref graph.
--verify-shas hits the GitHub commits API for each SHA-pinned reference and emits a warning when the SHA isn't reachable from the claimed repo's refs. It doesn't change resolution behavior — ABOM still builds the same dependency tree GitHub would — it just surfaces the discrepancy.
abom scan . --verify-shas --github-token $GITHUB_TOKENCombine with --fail-on-warnings to block CI on the finding:
abom scan . --verify-shas --fail-on-warnings --github-token $GITHUB_TOKENWhat a warning means: the SHA is not reachable from owner/repo's refs. That may be a fork-only commit, a force-pushed-away commit, or a mistaken pin. It does not necessarily mean the SHA was tampered with.
Exit codes: 0 clean, 1 compromised action (or runtime error), 2 warnings emitted with --fail-on-warnings. When both conditions hold, exit 1 wins.
Rate limit caveat: --verify-shas makes an extra API call per unique SHA. Anonymous requests are capped at 60/hour — set --github-token (or GITHUB_TOKEN) for a realistic 5000/hour budget.
Git tags are mutable. A workflow pinned to actions/checkout@v4 today may resolve to a different commit next week if the maintainer re-points the tag. This means a BOM with tag refs is only a semi-reliable record of what actually ran. Branches (main, master) are even more mutable.
For teams generating BOMs as audit or compliance evidence, --resolve-refs calls the GitHub commits API to look up the commit SHA each tag or branch currently points to and records it in resolved_sha alongside the original ref. The original pinning is preserved so contributor intent stays visible.
abom scan . --resolve-refs --github-token $GITHUB_TOKENOutput (JSON) gets a stable record of what was actually resolved at generation time:
{
"uses": "actions/checkout@v4",
"ref": "v4",
"ref_type": "tag",
"resolved_sha": "34e114876b0b11c390a56381ad16ebd13914f8d5"
}Scope: tag and branch refs only. SHA-pinned refs are already immutable and skipped. Docker and local actions are also skipped.
Interaction with --verify-shas: orthogonal. --verify-shas walks SHA-pinned refs; --resolve-refs populates resolved_sha for tag and branch refs. Running both together populates both signals.
Rate limit caveat: same as --verify-shas — one API call per unique tag or branch ref, so a token is effectively required for any workflow with more than a handful of tagged actions.
abom finds compromised dependencies through three layers that grep will never reach:
| Layer | What it catches | How |
|---|---|---|
| Direct | uses: aquasecurity/trivy-action@v0.20.0 |
Parses workflow YAML |
| Transitive | Composite action A calls action B which calls trivy-action |
Fetches and parses action.yml recursively |
| Embedded | crazy-max/ghaction-container-scan has a trivy_version input |
Scans action metadata for known tool signatures |
| Format | Flag | Use case |
|---|---|---|
| Table | -o table |
Human-readable dependency tree (default) |
| JSON | -o json |
Native ABOM format for automation |
| CycloneDX 1.5 | -o cyclonedx-json |
Dependency-Track, Grype, standard BOM tooling |
| SPDX 2.3 | -o spdx-json |
Compliance, license scanning, SBOM aggregation |
abom --check compares your ABOM against known-compromised actions:
- Built-in — a snapshot ships with every release. Works fully offline.
- Auto-updated — fetches the latest data from
advisories.juliet.shat runtime. Source and contributions atJulietSecurity/abom-advisories. - Community-curated — anyone can submit a PR to add a new advisory.
SHA-pinned actions and advisories. When --check encounters a SHA-pinned action that matches an advisory's affected package, abom resolves the SHA to its upstream tag (via git ls-remote, which doesn't consume REST API quota) and compares that tag against the advisory's affected version ranges. Refs at fixed versions are cleared; refs that fall inside the affected range are flagged with a definitive advisory ID instead of the older "verify manually" annotation. No extra flags required — this runs automatically whenever --check is used with network access.
See advisories.juliet.sh/db/advisories.json for the current advisory list.
| Flag | Short | Description | Default |
|---|---|---|---|
--output |
-o |
Output format: table, json, cyclonedx-json, spdx-json |
table |
--file |
-f |
Write output to file instead of stdout | stdout |
--check |
Flag known-compromised actions | false |
|
--depth |
-d |
Max recursion depth for transitive deps | 10 |
--verify-shas |
Verify pinned SHAs are reachable from upstream repo refs | false |
|
--resolve-refs |
Resolve tag and branch refs to current commit SHAs | false |
|
--fail-on-warnings |
Exit 2 if any warnings were emitted |
false |
|
--github-token |
GitHub token for API requests (also reads GITHUB_TOKEN) |
||
--no-network |
Skip resolving transitive dependencies (local parsing only) | false |
|
--offline |
Use built-in advisory data only, skip remote fetch | false |
|
--no-cache |
Force fresh advisory database fetch | false |
|
--quiet |
-q |
Suppress progress output, only print results | false |
We welcome contributions. If you discover a compromised action or a wrapper that abom should detect:
- Advisory data — submit a PR to
JulietSecurity/abom-advisories - Tool detection — add tool signatures in
pkg/parser/action.go - Bug reports and features — open an issue
abom is released under the Apache 2.0 License.
Built and maintained by Juliet Security
Website · GitHub · Contact