← Back to blog
2026-07-07 · VulnLedger

"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.

CI/CD GitHub Actions SBOM Security Scanning DevSecOps Supply Chain 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

  • Immediate feedback — developers see vulnerabilities before merging
  • Consistent enforcement — policies apply to every pull request, not just when someone remembers
  • Audit trail — every scan result is logged with commit hash and timestamp
  • Shift-left security — fix vulnerabilities when they are cheapest to fix (during development)

    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

  • name: Security Scan

    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 vulnledger

    2. Generate your first SBOM locally

    vulnledger scan --format cyclonedx

    3. Review the results

    cat sbom.json | python3 -m json.tool | head -50

    4. 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.

    Try VulnLedger

    Generate SBOMs and scan for vulnerabilities in one command.

    Start Free