Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .github/actions/sbom-update/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Generate SBOM
description: Generates CycloneDX SBOM using cdxgen

inputs:
output-file:
description: "Output filename for the SBOM"
required: false
default: "sbom.json"

outputs:
HAS_CHANGES:
description: "Whether the SBOM has meaningful changes compared to the existing version"
value: ${{ steps.generate.outputs.HAS_CHANGES }}

runs:
using: composite
steps:
- name: Generate SBOM
id: generate
shell: bash
env:
SBOM_OUTPUT_FILE: ${{ inputs.output-file }}
run: |
SCRIPT_DIR="${{ github.action_path }}"
chmod +x "${SCRIPT_DIR}/generate-sbom.sh"
"${SCRIPT_DIR}/generate-sbom.sh" "$SBOM_OUTPUT_FILE"
113 changes: 113 additions & 0 deletions .github/actions/sbom-update/generate-sbom.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#!/usr/bin/env bash
#
# generate-sbom.sh - Generate and validate CycloneDX SBOM
#
# Usage: ./generate-sbom.sh [output-file]
#
# Environment variables:
# GITHUB_OUTPUT - Path to GitHub Actions output file (optional)
#

set -euo pipefail

SBOM_FILE="${1:-sbom.json}"
TEMP_SBOM="sbom-new.json"
CYCLONEDX_CLI="/tmp/cyclonedx"
CYCLONEDX_CLI_URL="https://github.com/CycloneDX/cyclonedx-cli/releases/download/v0.29.1/cyclonedx-linux-x64"
JQ_NORMALIZER='del(.serialNumber) | del(.metadata.timestamp) | walk(if type == "object" and .timestamp then .timestamp = "TIMESTAMP_NORMALIZED" else . end)'

echo "Starting SBOM generation (output: $SBOM_FILE)"

echo "Generating SBOM for 'node' project..."

if ! npx @cyclonedx/cyclonedx-npm \
--omit dev \
--package-lock-only \
--output-file "$TEMP_SBOM" \
--output-format json \
--spec-version 1.5; then
echo "ERROR: Failed to generate SBOM" >&2
exit 1
fi

if [[ ! -f "$TEMP_SBOM" ]]; then
echo "ERROR: SBOM file not found after generation" >&2
exit 1
fi

echo "SBOM file generated: $TEMP_SBOM"

echo "Downloading CycloneDX CLI..."

if ! curl -L -s -o "$CYCLONEDX_CLI" "$CYCLONEDX_CLI_URL"; then
echo "ERROR: Failed to download CycloneDX CLI" >&2
exit 1
fi

chmod +x "$CYCLONEDX_CLI"

if [[ ! -x "$CYCLONEDX_CLI" ]]; then
echo "ERROR: CycloneDX CLI is not executable" >&2
exit 1
fi

echo "CycloneDX CLI ready at $CYCLONEDX_CLI"

echo "Validating SBOM: $TEMP_SBOM"

if ! "$CYCLONEDX_CLI" validate --input-file "$TEMP_SBOM" --fail-on-errors; then
echo "ERROR: SBOM validation failed for $TEMP_SBOM" >&2
exit 1
fi

echo "SBOM validation passed: $TEMP_SBOM"

echo "Checking for SBOM changes..."

HAS_CHANGES="false"

if [[ ! -f "$SBOM_FILE" ]]; then
echo "No existing $SBOM_FILE found, creating initial version"
mv "$TEMP_SBOM" "$SBOM_FILE"
HAS_CHANGES="true"
else
echo "Comparing new SBOM with existing $SBOM_FILE..."

# Try cyclonedx diff for component-level comparison
DIFF_OUTPUT=$("$CYCLONEDX_CLI" diff "$SBOM_FILE" "$TEMP_SBOM" --component-versions 2>/dev/null || true)

if echo "$DIFF_OUTPUT" | grep -q "^None$"; then
echo "No component changes detected via cyclonedx diff"

# Double-check with jq normalization (excludes metadata like timestamps)
if diff -q \
<(jq -r "$JQ_NORMALIZER" < "$SBOM_FILE") \
<(jq -r "$JQ_NORMALIZER" < "$TEMP_SBOM") > /dev/null 2>&1; then
echo "No meaningful changes detected in SBOM"
rm -f "$TEMP_SBOM"
HAS_CHANGES="false"
else
echo "Changes detected in SBOM (non-component changes)"
mv "$TEMP_SBOM" "$SBOM_FILE"
HAS_CHANGES="true"
fi
else
echo "Component changes detected:"
echo "$DIFF_OUTPUT"
mv "$TEMP_SBOM" "$SBOM_FILE"
HAS_CHANGES="true"
fi
fi

if [[ -n "${GITHUB_OUTPUT:-}" ]]; then
echo "HAS_CHANGES=${HAS_CHANGES}" >> "$GITHUB_OUTPUT"
fi
echo "Output: HAS_CHANGES=${HAS_CHANGES}"

if [[ ! -f "$SBOM_FILE" ]]; then
echo "ERROR: Final SBOM file not found at $SBOM_FILE" >&2
exit 1
fi

echo "SBOM file validated: $SBOM_FILE"
echo "SBOM generation completed successfully"
51 changes: 51 additions & 0 deletions .github/workflows/sbom.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Post-Merge SBOM Update
on:
push:
branches:
- main
paths:
- 'package.json'
- 'package-lock.json'

workflow_dispatch:

permissions:
contents: write

jobs:
sbom:
name: Generate SBOM and Create PR
runs-on: ubuntu-latest

concurrency:
group: sbom-update
cancel-in-progress: false

steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
ref: ${{ github.ref }}
token: ${{ secrets.GITHUB_TOKEN }}

- name: Install Node and dependencies
uses: mongodb-labs/drivers-github-tools/node/setup@v3
with:
ignore_install_scripts: false

- name: Generate SBOM
id: generate_sbom
uses: ./.github/actions/sbom-update
with:
output-file: sbom.json

- name: Commit SBOM changes
if: steps.generate_sbom.outputs.HAS_CHANGES == 'true'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add sbom.json
git commit -m "chore(deps): Update SBOM after dependency changes"
git push
echo "SBOM updated and committed" >> $GITHUB_STEP_SUMMARY
continue-on-error: true
Loading