What is SBOM?
SBOM (Software Bill of Materials) = the ingredient label of software.
It lists all parts *(open-source libraries, frameworks, dependencies, versions, licenses) * that make up an app or system.
Real-Life Example
Think of food packaging: you buy bread, and the label says “contains wheat, soy, nuts.”
In software, SBOM is that label: “contains React v18, Log4j v2.14, OpenSSL 1.1.1.”
If wheat is recalled (or Log4j is hacked), you know exactly which bread (or software) is risky.
Why Automating SBOMs Matters in 2025
In the U.S., Executive Order 14028 has made SBOMs mandatory for software vendors selling to federal agencies. In Europe, the NIS2 Directive and EU Cyber Resilience Act emphasize transparent software inventories. Across APAC, markets like Japan and Singapore are also aligning with SBOM-driven security standards.
By automating SBOM generation and vulnerability scanning:
- 🇺🇸 U.S. teams can meet NTIA and CISA requirements.
- 🇪🇺 EU developers stay ahead of compliance with Cyber Resilience mandates.
- Global enterprises ensure cross-border trust in their software supply chains.
The Workflow in Action
Github actions workflow triggers on pushes, pull requests, or manual runs. It automatically:
- Generates an SBOM in CycloneDX format
- Scans for vulnerabilities with Grype
- Uploads artifacts for compliance and audit readiness
name: Supply chain security scan
on:
push:
branches: [main]
paths:
- '**/*.rs'
- Cargo.toml
- Cargo.lock
- scripts/security-scan.sh
- .github/actions/security-scan/**
- .github/workflows/security-scan.yml
pull_request:
paths:
- '**/*.rs'
- Cargo.toml
- Cargo.lock
- scripts/security-scan.sh
- .github/actions/security-scan/**
- .github/workflows/security-scan.yml
workflow_dispatch:
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Generate SBOM and scan for vulnerabilities
uses: ./.github/actions/security-scan
with:
bom-file: shadowmap-bom.json
report-file: grype-report.json
fail-on: medium
enable-reachability-analysis: true
- name: Upload scan artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: shadowmap-security-scan
path: |
shadowmap-bom.json
grype-report.json
if-no-files-found: error
This ensures that every commit and pull request is scanned for vulnerabilities before entering the main branch.
In Action:
Press enter or click to view image in full size
The Custom Composite Action
To keep the workflow modular and reusable, the security logic is packaged as a composite GitHub Action.
name: "ShadowMap Security Scan"
description: "Generate a CycloneDX SBOM and scan it with Grype"
inputs:
bom-file:
description: "Filename for the generated CycloneDX SBOM"
required: false
default: "bom.json"
report-file:
description: "Optional JSON file to store Grype results"
required: false
default: ""
runs:
using: "composite"
steps:
- uses: dtolnay/rust-toolchain@stable
- name: Ensure cargo-cyclonedx is installed
shell: bash
run: |
if ! cargo cyclonedx --version >/dev/null 2>&1; then
cargo install cargo-cyclonedx --locked
fi
- name: Ensure grype is installed
shell: bash
run: |
if ! command -v grype >/dev/null 2>&1; then
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sudo sh -s -- -b /usr/local/bin
fi
- name: Run security scan
shell: bash
run: |
ARGS=("${{ inputs.bom-file }}")
if [[ -n "${{ inputs.report-file }}" ]]; then
ARGS+=("${{ inputs.report-file }}")
fi
./scripts/security-scan.sh "${ARGS[@]}
This ensures cargo-cyclonedx and grype are available, then delegates the heavy lifting to the custom script.
The Security Scan Script
The script scripts/security-scan.sh handles SBOM generation and vulnerability scanning.
#!/usr/bin/env bash
set -euo pipefail
BOM_FILENAME="${1:-bom.json}"
REPORT_FILENAME="${2:-}" # optional second arg for JSON report
if [[ "$BOM_FILENAME" = /* ]]; then
BOM_PATH="$BOM_FILENAME"
else
BOM_PATH="$PWD/$BOM_FILENAME"
fi
BOM_DIR="$(dirname "$BOM_PATH")"
BOM_BASENAME="$(basename "$BOM_PATH")"
case "$BOM_BASENAME" in
*.json) BOM_EXTENSION="json" ;;
*.xml) BOM_EXTENSION="xml" ;;
*) BOM_EXTENSION="json"
BOM_BASENAME="$BOM_BASENAME.json"
BOM_PATH="$BOM_DIR/$BOM_BASENAME"
;;
esac
BOM_STEM="${BOM_BASENAME%.*}"
if [[ -z "$BOM_STEM" ]]; then
echo "SBOM filename must include a base name (got '$BOM_BASENAME')." >&2
exit 1
fi
if [[ -n "$REPORT_FILENAME" ]]; then
if [[ "$REPORT_FILENAME" = /* ]]; then
REPORT_PATH="$REPORT_FILENAME"
else
REPORT_PATH="$PWD/$REPORT_FILENAME"
fi
REPORT_DIR="$(dirname "$REPORT_PATH")"
mkdir -p "$REPORT_DIR"
else
REPORT_PATH=""
fi
command_exists() { command -v "$1" >/dev/null 2>&1; }
if ! command_exists cargo; then
echo "cargo is required to generate the SBOM." >&2
exit 1
fi
if ! cargo cyclonedx --version >/dev/null 2>&1; then
echo "cargo-cyclonedx is not installed. Install it with: cargo install cargo-cyclonedx" >&2
exit 1
fi
if ! command_exists grype; then
echo "grype is not installed. Install it with: curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sudo sh -s -- -b /usr/local/bin" >&2
exit 1
fi
# Generate the SBOM
mkdir -p "$BOM_DIR"
GENERATED_NAME="$BOM_STEM.$BOM_EXTENSION"
GENERATED_PATH="$PWD/$GENERATED_NAME"
rm -f "$GENERATED_PATH"
cargo cyclonedx \
--format "$BOM_EXTENSION" \
--spec-version 1.5 \
--all-features \
--override-filename "$BOM_STEM"
if [[ ! -f "$GENERATED_PATH" ]]; then
echo "cargo-cyclonedx did not produce $GENERATED_PATH" >&2
exit 1
fi
if [[ "$GENERATED_PATH" != "$BOM_PATH" ]]; then
mv "$GENERATED_PATH" "$BOM_PATH"
fi
echo "Generated SBOM at $BOM_PATH"
# Scan SBOM with Grype
if [[ -n "$REPORT_PATH" ]]; then
grype "sbom:$BOM_PATH" -o json --file "$REPORT_PATH"
echo "Stored vulnerability report at $REPORT_PATH"
else
grype "sbom:$BOM_PATH"
fi
This script enforces strict validation, generates a CycloneDX SBOM, and scans it with Grype. If a report file is provided, results are stored in JSON for further analysis.
Benefits for Developers and Security Teams
🔎 Full transparency of dependencies, including transitive Rust crates.
⚡ Faster incident response when new CVEs are disclosed.
📊 Audit-ready reports for compliance across U.S., EU, and APAC.
🛡️ Reduced supply chain risk by embedding checks directly into CI/CD.
FAQs
Q: What is an SBOM in Rust development?
A: An SBOM is a Software Bill of Materials — a list of all dependencies in your Rust project, generated automatically with cargo-cyclonedx.
Q: How does ShadowMap scan for vulnerabilities?
A: It uses Grype, a vulnerability scanner, to analyze the SBOM and report security risks.
Q: Can ShadowMap integrate with global compliance standards?
A: Yes. It supports CycloneDX and SPDX, aligning with NTIA (U.S.), NIS2 (EU), and APAC security frameworks.
Q: Is ShadowMap only for Rust projects?
A: While optimized for Rust, the workflow structure can be extended to other ecosystems.
Conclusion
Dependencies power every Rust project, but they can also open the door to attacks. ShadowMap Security Scan closes that gap by integrating SBOM generation, vulnerability scanning, and artifact management directly into your GitHub Actions pipeline.
Here's a video overview of the above :
👉 Inspired by insights from Salem B., we built and shared this framework with the community.
👉 If you are serious about DevSecOps, start integrating SBOMs and vulnerability scans into your pipelines today.
👉 Want the bigger picture? Check out our earlier write-up on ShadowMap here: Read the documentation
Top comments (0)