"Python CI/CD Security: GitHub Actions for Python Projects"
Complete guide to setting up security scanning in GitHub Actions for Python projects. Covers dependency scanning, secret detection, SAST, SBOM generation, and security gates.
You've secured your Python code, scanned your dependencies, and hardened your containers. But none of that matters if an insecure commit slips through to production. CI/CD security gates are the final defense.
This guide shows you how to set up a complete Python security pipeline in GitHub Actions — from dependency scanning to SBOM generation, in one YAML file.
Why Python CI/CD Security Matters
Without automated security checks in CI/CD:
- A developer pushes code with a known CVE → nobody catches it
- A dependency gets updated → new vulnerability introduced
- A secret gets committed → leaked to the public
- An urgent hotfix skips security review → ships with issues
With CI/CD security gates:
- Every commit is scanned automatically
- Critical vulnerabilities fail the build
- SBOMs are generated for every release
- Security becomes part of the development process
The Complete Python Security Pipeline
name: Python Security Gate
on:
pull_request:
branches: [main, master]
push:
branches: [main, master]
jobs:
security:
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 security tools
run: pip install vulnledger detect-secrets bandit
- name: Scan dependencies
run: vulnledger scan . --ci
- name: Scan for secrets
run: detect-secrets scan --all-files
- name: Run security linter
run: bandit -r . --severity-level medium --exit-zero
- name: Run tests
run: pytest --cov
- name: Generate SBOM
run: vulnledger scan . --json --output sbom.json
- name: Upload SBOM
uses: actions/upload-artifact@v4
if: github.event_name == 'push'
with:
name: sbom-${{ github.sha }}
path: sbom.json
What Each Step Does
1. Dependency Scanning
- name: Scan dependencies
run: vulnledger scan . --ci
Checks every dependency (direct + transitive) against OSV.dev. The --ci flag fails the build if critical/high CVEs are found.
2. Secret Detection
- name: Scan for secrets
run: detect-secrets scan --all-files
Scans every file for hardcoded API keys, passwords, and tokens. Prevents secrets from reaching production.
3. Security Linting
- name: Run security linter
run: bandit -r . --severity-level medium --exit-zero
Python-specific security linting. Catches injection risks, insecure functions, and common security mistakes. --exit-zero means it reports but doesn't fail the build (you can remove this flag to make it a gate).
4. Tests
- name: Run tests
run: pytest --cov
Tests catch regressions. If a dependency upgrade breaks something, the tests catch it.
5. SBOM Generation
- name: Generate SBOM
run: vulnledger scan . --json --output sbom.json
Generates a CycloneDX SBOM for every build. Attach to releases for compliance.
Making It a Security Gate
To fail the build on security issues, remove --exit-zero from bandit and keep --ci on VulnLedger:
- name: Security gate
run: |
vulnledger scan . --ci # Fails on critical/high CVEs
bandit -r . --severity-level high # Fails on high severity issues
Adding to Pull Request Checks
Configure branch protection to require the security job:
1. Go to repo Settings → Branches → main
2. Enable "Require status checks to pass"
3. Add "security" as a required check
Now no PR can merge without passing security scans.
Scheduled Security Scans
Scan weekly for new CVEs:
on:
schedule:
- cron: '0 8 * * 1' # Monday at 8 AM
workflow_dispatch:
jobs:
weekly-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pip install vulnledger
- run: vulnledger scan . --json --output weekly-scan.json
- uses: actions/upload-artifact@v4
with:
name: weekly-scan
path: weekly-scan.json
Best Practices
1. Scan on every PR — not just main
2. Fail the build — critical CVEs must block merges
3. Generate SBOMs — attach to every release
4. Schedule scans — catch new CVEs weekly
5. Keep tools updated — pip install --upgrade vulnledger
6. Cache dependencies — speed up CI runs
Python-Specific CI/CD Tips
Cache pip packages
- name: Cache pip
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
Use matrix testing
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
Publish SBOM to releases
- name: Upload SBOM to release
uses: softprops/action-gh-release@v2
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
with:
files: sbom.json
How VulnLedger Fits In
VulnLedger provides the core security scanning for your Python CI/CD pipeline:
- Dependency scanning — catches CVEs before they reach production
- SBOM generation — compliance-ready dependency inventory for every build
- CI gate — fails builds on critical/high CVEs
- Works with all Python package managers — pip, Poetry, pipenv, conda
- name: Security scan
run: |
pip install vulnledger
vulnledger scan . --ci
vulnledger scan . --json --output sbom.json
Conclusion
Python CI/CD security is the final defense against vulnerable code reaching production. One YAML file adds dependency scanning, secret detection, security linting, testing, and SBOM generation. The setup takes 10 minutes. The protection is permanent.
This is the final post in the "Python Security Handbook" series. Here's the complete list:
1. Python Security Best Practices 2026 — Overview
2. Securing Your Python Dependencies — Dependency security
3. Python Web App Security: Flask, FastAPI, Django — Web hardening
4. Python Container Security: Docker + Python — Container security
5. This post — CI/CD automation
Together with all other series, you now have 27 blog posts covering:
- How to Write Safe Software (5 posts)
- Open Source Security (4 posts)
- The SBOM Handbook (6 posts)
- Vibe Coding (3 posts)
- Python Security Handbook (5 posts)
- Standalone: Dependabot, Container Scanning, CI/CD
That's 27 posts forming a comprehensive security knowledge base, all linking to each other and to VulnLedger.