feat(security): add comprehensive SBOM generation and integration#11
feat(security): add comprehensive SBOM generation and integration#11
Conversation
- Add dedicated security-sbom.yml workflow for multi-format SBOM generation - Generate SBOMs in SPDX, CycloneDX, and Syft formats - Integrate Syft SBOM generation into container security workflow - Add CycloneDX SBOM generation to dependency workflow - Include SBOM vulnerability scanning with Grype - Add license compliance tracking - Update security report to aggregate SBOM data - Support SBOM attestation for main branch pushes - Publish SBOMs as release assets
There was a problem hiding this comment.
Pull Request Overview
This PR adds comprehensive Software Bill of Materials (SBOM) generation and vulnerability scanning capabilities to the security workflow infrastructure. The implementation creates a dedicated SBOM workflow while integrating SBOM generation into existing container and dependency security workflows.
- Implements comprehensive SBOM generation using multiple tools (Syft, cdxgen, CycloneDX) with support for various formats (SPDX, CycloneDX, Syft native)
- Integrates vulnerability scanning of SBOMs using Grype and adds SBOM attestation for supply chain security
- Updates existing security workflows to include SBOM generation and enhances security reporting with supply chain tracking
Reviewed Changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
.github/workflows/security-sbom.yml |
New comprehensive SBOM workflow with source code, container, and vulnerability scanning jobs |
.github/workflows/security-report.yml |
Added SBOM analysis section to aggregate and display supply chain information |
.github/workflows/security-dependencies.yml |
Integrated CycloneDX SBOM generation for npm dependencies |
.github/workflows/security-containers.yml |
Added Syft SBOM generation alongside existing container scanning |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| find . -name "vuln-*.json" | while read vuln_file; do | ||
| if [ -f "$vuln_file" ]; then | ||
| CRITICAL=$(jq '[.matches[] | select(.vulnerability.severity == "Critical")] | length' $vuln_file 2>/dev/null || echo "0") | ||
| HIGH=$(jq '[.matches[] | select(.vulnerability.severity == "High")] | length' $vuln_file 2>/dev/null || echo "0") | ||
| MEDIUM=$(jq '[.matches[] | select(.vulnerability.severity == "Medium")] | length' $vuln_file 2>/dev/null || echo "0") | ||
| LOW=$(jq '[.matches[] | select(.vulnerability.severity == "Low")] | length' $vuln_file 2>/dev/null || echo "0") | ||
|
|
||
| TOTAL_CRITICAL=$((TOTAL_CRITICAL + CRITICAL)) | ||
| TOTAL_HIGH=$((TOTAL_HIGH + HIGH)) | ||
| TOTAL_MEDIUM=$((TOTAL_MEDIUM + MEDIUM)) | ||
| TOTAL_LOW=$((TOTAL_LOW + LOW)) | ||
| fi | ||
| done |
There was a problem hiding this comment.
The variables TOTAL_CRITICAL, TOTAL_HIGH, TOTAL_MEDIUM, and TOTAL_LOW are modified inside a subshell created by the pipe to while read. These changes won't persist outside the loop, so the totals will always remain 0. Use process substitution instead: while read vuln_file; do ... done < <(find . -name \"vuln-*.json\")
| find . -name "vuln-*.json" | while read vuln_file; do | |
| if [ -f "$vuln_file" ]; then | |
| CRITICAL=$(jq '[.matches[] | select(.vulnerability.severity == "Critical")] | length' $vuln_file 2>/dev/null || echo "0") | |
| HIGH=$(jq '[.matches[] | select(.vulnerability.severity == "High")] | length' $vuln_file 2>/dev/null || echo "0") | |
| MEDIUM=$(jq '[.matches[] | select(.vulnerability.severity == "Medium")] | length' $vuln_file 2>/dev/null || echo "0") | |
| LOW=$(jq '[.matches[] | select(.vulnerability.severity == "Low")] | length' $vuln_file 2>/dev/null || echo "0") | |
| TOTAL_CRITICAL=$((TOTAL_CRITICAL + CRITICAL)) | |
| TOTAL_HIGH=$((TOTAL_HIGH + HIGH)) | |
| TOTAL_MEDIUM=$((TOTAL_MEDIUM + MEDIUM)) | |
| TOTAL_LOW=$((TOTAL_LOW + LOW)) | |
| fi | |
| done | |
| done < <(find . -name "vuln-*.json") |
| echo "Build failed, creating minimal image..." | ||
| cat > Dockerfile.minimal << EOF | ||
| FROM node:18-alpine | ||
| WORKDIR /app | ||
| COPY ${{ matrix.service }}/package*.json ./ | ||
| RUN npm ci --only=production || npm install --production || echo "Install failed" | ||
| EOF | ||
| docker build -t connectkit-${{ matrix.service }}:sbom -f Dockerfile.minimal . |
There was a problem hiding this comment.
The fallback Docker build strategy assumes a specific project structure and Node.js setup that may not match the actual project. Consider validating the existence of package.json files before attempting the fallback build, or make the fallback more generic to handle different project types.
| echo "Build failed, creating minimal image..." | |
| cat > Dockerfile.minimal << EOF | |
| FROM node:18-alpine | |
| WORKDIR /app | |
| COPY ${{ matrix.service }}/package*.json ./ | |
| RUN npm ci --only=production || npm install --production || echo "Install failed" | |
| EOF | |
| docker build -t connectkit-${{ matrix.service }}:sbom -f Dockerfile.minimal . | |
| echo "Build failed, checking for Node.js project files..." | |
| if ls ${{ matrix.service }}/package*.json 1> /dev/null 2>&1; then | |
| echo "Node.js project detected, creating minimal Node.js image..." | |
| cat > Dockerfile.minimal << EOF | |
| FROM node:18-alpine | |
| WORKDIR /app | |
| COPY ${{ matrix.service }}/package*.json ./ | |
| RUN npm ci --only=production || npm install --production || echo "Install failed" | |
| EOF | |
| docker build -t connectkit-${{ matrix.service }}:sbom -f Dockerfile.minimal . | |
| else | |
| echo "No package.json found, creating generic minimal image..." | |
| cat > Dockerfile.minimal << EOF | |
| FROM alpine:3.18 | |
| WORKDIR /app | |
| COPY ${{ matrix.service }} . | |
| EOF | |
| docker build -t connectkit-${{ matrix.service }}:sbom -f Dockerfile.minimal . | |
| fi |
| TOTAL_COMPONENTS=$((TOTAL_COMPONENTS + COMPONENTS)) | ||
| fi | ||
| done | ||
|
|
There was a problem hiding this comment.
Similar to the previous vulnerability counting issue, the TOTAL_COMPONENTS variable is being modified inside a subshell created by the for loop with glob expansion. The changes won't persist outside the loop. Use an array or process substitution approach to properly accumulate the component counts.
| TOTAL_COMPONENTS=$((TOTAL_COMPONENTS + COMPONENTS)) | |
| fi | |
| done | |
| COMPONENT_COUNTS+=("$COMPONENTS") | |
| fi | |
| done | |
| TOTAL_COMPONENTS=0 | |
| for count in "${COMPONENT_COUNTS[@]}"; do | |
| TOTAL_COMPONENTS=$((TOTAL_COMPONENTS + count)) | |
| done |
- Add Node.js version check before cdxgen installation - Skip cdxgen on Node 18, use alternative CycloneDX npm tool - Ensure workflow continues even if cdxgen is incompatible - Add fallback for environments with Node.js < 20
- Remove archived/accessibility.yml (now active) - Remove archived/ci.yml (replaced with separate workflows) - Remove archived/compliance-federal.yml (not needed) - Remove archived/nightly.yml (not needed) - Remove archived/performance.yml (not needed) - Remove archived/security.yml (replaced with multiple security workflows) - Clean up repository by removing unused workflow files
Summary
Changes
New Workflow: security-sbom.yml
Updated Workflows
SBOM Formats Supported
Testing
All workflows include:
continue-on-error: trueBenefits