"CI/CD Security Scanning: A Complete Guide to GitHub Actions Integration"
How to integrate SBOM generation and vulnerability scanning into your GitHub Actions CI/CD pipeline. Step-by-step guide with real workflow examples for automated security.
Security scanning should happen every time code changes — not just when someone remembers to run it manually. By integrating SBOM generation and vulnerability scanning into your CI/CD pipeline, you catch security issues before they reach production.
This guide shows you how to set up automated security scanning in GitHub Actions, with practical workflow examples you can copy and customize.
Why CI/CD Security Scanning Matters
Manual security reviews do not scale. As projects grow and teams ship faster, the gap between "last security scan" and "current code" widens. Automated CI/CD scanning closes this gap:
- Every commit gets scanned — no code slips through without security review
The CI/CD Security Pipeline
A complete CI/CD security pipeline typically includes these stages:
1. SBOM Generation
Generate a Software Bill of Materials when dependencies change. The SBOM captures every component your project depends on — direct and transitive.
2. Vulnerability Scanning
Check every component in your SBOM against known vulnerability databases (OSV, NVD, GitHub Advisory Database).
3. Policy Enforcement
Compare scan results against your security policies. Should any critical vulnerabilities block deployment? What about license compliance?
4. Reporting and Alerts
Notify the team about new vulnerabilities, policy violations, and compliance status.
GitHub Actions Workflow: Basic Setup
Here is a minimal GitHub Actions workflow that generates an SBOM and scans for vulnerabilities on every push:
`yaml
on: push: branches: [main] pull_request: branches: [main]
jobs: security-scan: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4
- name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.12"
- name: Install VulnLedger CLI run: pip install vulnledger
- name: Run SBOM generation and scan run: vulnledger scan --format cyclonedx --output sbom.json
- name: Upload SBOM artifact
uses: actions/upload-artifact@v4
with:
name: sbom
path: sbom.json
`
This workflow runs on every push and pull request to main. It generates a CycloneDX SBOM and uploads it as a build artifact.
Advanced Workflow: Policy Enforcement
For production use, you want to fail the build when critical vulnerabilities are found:
`yaml
name: Security Gate
on: pull_request: branches: [main]
jobs: security-gate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.12"
- name: Install VulnLedger CLI run: pip install vulnledger
- name: Scan for vulnerabilities run: vulnledger scan --output report.json --format cyclonedx continue-on-error: true
- name: Check security gate run: | CRITICAL=$(python3 -c "import json; d=json.load(open('report.json')); print(d.get('summary',{}).get('critical',0))") if [ "$CRITICAL" -gt 0 ]; then echo "FAIL: $CRITICAL critical vulnerabilities found" echo "Fix critical vulnerabilities before merging" exit 1 fi echo "PASS: No critical vulnerabilities"
- name: Upload report
uses: actions/upload-artifact@v4
if: always()
with:
name: security-report
path: report.json
`
This workflow blocks pull requests that introduce critical vulnerabilities while allowing non-critical issues to pass with a warning.
Multi-Language Support
Modern projects often use multiple programming languages. GitHub Actions can scan all of them:
`yaml
name: Multi-Language Security Scan
on: push: branches: [main]
jobs: scan-python: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.12" - run: pip install vulnledger - run: vulnledger scan --output python-sbom.json
scan-node: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: "20" - run: npm install - run: pip install vulnledger - run: vulnledger scan --output node-sbom.json
scan-go:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.22"
- run: pip install vulnledger
- run: vulnledger scan --output go-sbom.json
`
Each language gets its own job, and the SBOMs are uploaded as separate artifacts. This parallel approach keeps CI fast while maintaining comprehensive coverage.
Scheduled Scanning
Even code that does not change can become vulnerable when new CVEs are published. Add scheduled scanning to catch these:
`yaml
name: Scheduled Vulnerability Check
on: schedule: - cron: "0 8 1" # Every Monday at 8 AM
jobs: weekly-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.12"
- name: Install VulnLedger CLI run: pip install vulnledger
- name: Full scan with latest vuln data run: vulnledger scan --output weekly-report.json
- name: Create GitHub issue if critical vulns found
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const report = JSON.parse(fs.readFileSync('weekly-report.json'));
const critical = report.summary?.critical || 0;
if (critical > 0) {
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: Weekly Security Alert: ${critical} critical vulnerabilities,
body: Weekly scan found ${critical} critical vulnerabilities. Please review and remediate.,
labels: ['security', 'critical']
});
}
`
This runs every Monday morning and automatically creates a GitHub issue when critical vulnerabilities are discovered.
Integrating with VulnLedger Dashboard
For teams using VulnLedger's cloud dashboard, you can push scan results directly:
`yaml
- name: Scan and upload to VulnLedger
env:
VULNLEDGER_API_KEY: ${{ secrets.VULNLEDGER_API_KEY }}
run: |
vulnledger scan --upload --api-key $VULNLEDGER_API_KEY
`
This sends your SBOM and scan results to VulnLedger's dashboard for centralized tracking, alerting, and compliance reporting across all your projects.
Best Practices
1. Scan on Every Pull Request
Never merge code without scanning it first. Configure your GitHub branch protection rules to require the security scan job to pass before merging.
2. Store SBOMs as Artifacts
Upload SBOMs as GitHub Actions artifacts. This creates an audit trail and lets you compare SBOMs across versions.
3. Use Secrets for API Keys
Never hardcode API keys or tokens in workflow files. Use GitHub Secrets:
`yaml
env:
VULNLEDGER_API_KEY: ${{ secrets.VULNLEDGER_API_KEY }}
`
4. Set Timeouts
Security scans should not run forever. Set reasonable timeouts to prevent runaway jobs:
`yaml
jobs:
security-scan:
runs-on: ubuntu-latest
timeout-minutes: 15
`
5. Cache Dependencies
Cache pip packages and other dependencies to speed up your security scans:
`yaml
- name: Cache pip
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
`
6. Fail Loud
When a security issue is found, make it obvious. Use clear error messages, appropriate exit codes, and visible annotations:
`yaml
- name: Annotate PR with vulnerabilities
if: failure()
run: |
echo "::error::Critical vulnerabilities found. See scan report for details."
`
Common Pitfalls
Scanning only on main branch — You need to scan pull requests too, not just after merging. Scanning only on main means vulnerabilities get merged first and detected second.
Ignoring transitive dependencies — Direct dependencies get updated, but transitive dependencies (dependencies of your dependencies) are often forgotten. A full SBOM captures all levels.
No scheduled scanning — New CVEs are published daily. A project with zero vulnerabilities today could have critical vulnerabilities tomorrow. Schedule weekly scans.
Overly strict policies — If every build fails, developers will bypass security checks. Start with critical-only blocking and expand gradually.
Getting Started Today
The fastest way to add security scanning to your GitHub Actions pipeline:
`bash
1. Install the CLI
pip install vulnledger2. Generate your first SBOM locally
vulnledger scan --format cyclonedx3. Review the results
cat sbom.json | python3 -m json.tool | head -504. Copy the GitHub Actions workflow to your repo
5. Push and watch the security scan run automatically
`For teams wanting centralized dashboards, alerts, and compliance reporting, VulnLedger's cloud dashboard integrates directly with the CLI. Sign up at vulnledger.com — free for open-source projects.
Conclusion
CI/CD security scanning is no longer optional — it is a baseline requirement for any serious software project. By integrating SBOM generation and vulnerability scanning into GitHub Actions, you get automated security enforcement without slowing down your team. The investment in setup pays for itself the first time a critical vulnerability is caught before reaching production.