DEV Community

uknowWho
uknowWho

Posted on

Automating SBOM Generation and Vulnerability Analysis

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:

  1. 🇺🇸 U.S. teams can meet NTIA and CISA requirements.
  2. 🇪🇺 EU developers stay ahead of compliance with Cyber Resilience mandates.
  3. 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:

  1. Generates an SBOM in CycloneDX format
  2. Scans for vulnerabilities with Grype
  3. 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
Enter fullscreen mode Exit fullscreen mode

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[@]}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)