Security Scanning in CI
Add security scanning to your CI pipeline: tools, setup, and best practices.
Focus: integrate security scanning into ci
You've automated your builds, tests, and deployments — but your pipeline is still shipping code with known vulnerabilities. The average modern application has hundreds of transitive dependencies, and a single insecure package can expose your entire system. In this lesson, you'll learn how to integrate security scanning into your CI pipeline so that every commit is automatically checked for vulnerabilities, secrets, and misconfigurations — catching problems before they ever reach production.
The problem this lesson solves
Security breaches are often traced back to a vulnerability that sat in the codebase for months. Manual security reviews don't scale, and security scanners run only occasionally (if at all) miss the window between commits. The result: your codebase accumulates risk silently, and by the time you notice, an attacker may have already exploited it.
Why you should care now: In a CI/CD environment, every merge to main triggers deployments. Without automated security gates, you are one vulnerable dependency or hardcoded secret away from a costly incident. Integrating security scanning into CI turns security from an afterthought into a first-class citizen of your delivery process, ensuring that security checks happen on every commit, not just during periodic audits.
This lesson addresses three critical problems: 1. Dependency vulnerabilities — packages with known CVEs that slip through because no one checks them. 2. Secret leakage — API keys, passwords, and tokens accidentally committed to the repository. 3. Configuration misconfigurations — cloud settings, Dockerfiles, or Kubernetes manifests that are insecure by default.
Core concept / mental model
Think of security scanning in CI as a automated security guard at the factory door. Every product that leaves the factory (your commit) passes through the guard. If the guard finds a problem (a vulnerability, a secret, a misconfiguration), the product is sent back for rework — the build fails, blocking the merge.
Key terms:
- SCA (Software Composition Analysis): Scans your dependencies (e.g., pip, npm) against known vulnerability databases like the NVD (National Vulnerability Database) or vendor advisories.
- SAST (Static Application Security Testing): Analyzes your source code for insecure patterns, like SQL injection or unsafe use of eval.
- Secret scanning: Detects secrets in your codebase — API keys, passwords, SSH private keys — and prevents them from being exposed.
- Container scanning: Checks Docker images for vulnerable OS packages and misconfigurations.
How they fit together: In your pipeline, you want to run these scans at the right stage. Dependency and secret scans should run as early as possible (on every push), while container scans can run right before image push. The output is typically a report with a severity level (critical, high, medium, low), and your pipeline can fail if a certain threshold is exceeded.
How it works step by step
Integrating security scanning into CI follows a standard pattern, regardless of the tool. Here's the logical sequence:
- Choose your scanner(s) — Pick tools that match your tech stack. For Python,
pip-auditscans PyPI dependencies; for containers,Trivy; for general source,Bandit. - Configure the scanner — Define what to scan, which severity to fail on, and where to output the report (JSON, SARIF).
- Run it in CI — Add a step to your pipeline (e.g., GitHub Actions, GitLab CI, Jenkins) that runs the scanner on every commit or pull request.
- Set up failure thresholds — Decide the policy: fail if any CRITICAL vulnerabilities found, or fail if HIGH or above.
- Upload reports and notifications — Make the results visible to developers (e.g., SARIF upload to GitHub) and send alerts on Slack.
- Remediate and track — Use the scan results to fix issues, and track trends over time.
For a Python project, a minimal pipeline step might look like:
# .github/workflows/security.yml
name: Security Scan
on: [push, pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install pip-audit
- name: Run dependency scan
run: pip-audit
This runs pip-audit on every push, and the job fails if any known vulnerable package is found.
Hands-on walkthrough
Let's integrate a real security scanning setup into a sample CI pipeline. We'll use GitHub Actions and three tools: pip-audit for dependencies, Bandit for SAST, and gitleaks for secrets.
1. Create a sample Python project
mkdir secure-ci-demo && cd secure-ci-demo
echo -e "requests==2.31.0\nflask==2.2.5" > requirements.txt
echo '{"name": "secure-ci-demo", "version": "1.0.0", "dependencies": {"requests": "2.31.0", "flask": "2.2.5"}}' > package.json
2. Add a GitHub Actions workflow
Create .github/workflows/security.yml:
name: Security Scan
on: [push, pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install tools
run: |
pip install bandit pip-audit
# Install gitleaks binary
wget -O gitleaks.tar.gz https://github.com/gitleaks/gitleaks/releases/download/v8.16.3/gitleaks_8.16.3_linux_x64.tar.gz
tar -xzf gitleaks.tar.gz
sudo mv gitleaks /usr/local/bin/
- name: Install dependencies (for pip-audit)
run: pip install -r requirements.txt
- name: Run pip-audit
run: pip-audit
- name: Run Bandit
run: bandit -r . -f json -o bandit-report.json
- name: Run gitleaks
run: gitleaks detect --source . --report-path gitleaks-report.json --report-format json
- name: Upload reports
uses: actions/upload-artifact@v3
with:
name: security-reports
path: |
bandit-report.json
gitleaks-report.json
3. Trigger the workflow
Push this to GitHub and watch the Actions run. On success, you'll see all three steps pass. On failure (e.g., if a vulnerable dependency is added), the pipeline stops, preventing the merge.
4. Expected output (sample)
When you run pip-audit locally:
Found 1 known vulnerability in 2 packages
Name Version ID Fix
flask 2.2.5 PYSEC-2023-62 2.2.5 (no fix)
And in the CI logs, you'll see:
pip-audit: 1 vulnerability found, failing job.
Pro tip: Use
pip-audit --require-hashesto ensure package integrity, but know it can fail on stale lock files.
Compare options / when to choose what
There are many security scanning tools; choosing depends on your stack and threat model. Here's a comparison of common ones:
| Tool | Type | Strengths | Weaknesses | Best for |
|---|---|---|---|---|
pip-audit |
SCA | Fast, Python-native, no DB needed | Only Python dependencies | Python projects |
Bandit |
SAST | Catches Python-specific patterns | Limited to AST, false positives | Python source code |
Trivy |
Container & fs | Scans OS packages, IaC; wide coverage | Heavier, needs Docker for full scan | Containerized apps |
gitleaks |
Secret scan | Fast, regex & entropy, supports many formats | May miss some custom secrets | Any repo |
Snyk |
SCA + SAST | Massive DB, supports many languages | Paid features, external dependency | Multi-language orgs |
Semgrep |
SAST | Custom rules, multi-language | Learning curve | Advanced teams |
When to choose what:
- Start with pip-audit if you're Python-only and want zero-config scanning.
- Add Bandit when you have business logic that might be insecure (e.g., input handling).
- Use Trivy if you build Docker images — it catches OS-level CVEs.
- Use Snyk if you need a single dashboard across multiple repositories and languages.
Pro tip: For a robust setup, combine at least one SCA tool and one secret scanner. They catch different classes of issues.
Troubleshooting & edge cases
Common issues when integrating security scanning into CI:
- Scan fails due to false positives: Tools like Bandit sometimes flag safe code. Mitigate by adding inline comments (
# nosec) or configure exclusions, but do so with a review. - Too many vulnerabilities to fix at once: If your historical code has many issues, start by setting the threshold to fail only on critical vulnerabilities, then gradually lower it.
- Scanner doesn't support your language: e.g.,
pip-auditonly works with Python. Find alternatives likenpm auditfor Node orTrivyfor any OS/package. - Secret scanner flags test secrets: Use a
.gitleaks.tomlconfig to allow specific patterns (e.g.,test_prefix). - CI step times out: Scanning large repos can take a while. Cache dependencies and scan artifacts to speed it up.
- Fail build but no one reads the report: Upload reports as artifacts and add a comment to PRs using tools like
sarif-uploador a custom action.
Edge case: If your pipeline scans a monorepo with multiple languages, you'll need to run multiple scanners, each with its own configuration — don't try to force one tool for everything.
What you learned & what's next
In this lesson, you learned how to integrate security scanning into your CI pipeline, covering the core concept of automated security gates, step-by-step implementation with GitHub Actions, and tool comparison. You can now:
- Explain the core idea behind security scanning in CI: continuous automated checks for vulnerabilities, secrets, and misconfigurations.
- Apply the practical exercise of adding scanning steps to a pipeline using
pip-audit,Bandit, andgitleaks. - Connect this to the next lessons in the track, where you'll dive deeper into deployment strategies and advanced pipeline orchestration.
Next step: Explore deployment strategies — you'll learn how to roll out your securely scanned code to production with minimal risk, using blue-green deployments or canary releases.
Practice recap
Create a new GitHub repository and add the security workflow from this lesson to an existing Python project with a couple of dependencies. Replace the sample requirements with a deliberately vulnerable package (e.g., Flask 2.2.4) and observe the pipeline fail. Then fix the dependency and watch the build turn green — you've just integrated security scanning into your CI!
Common mistakes
- Running security scans only on merge to main, not on every pull request — you miss vulnerabilities in dev branches.
- Ignoring scan results until the build fails and then blindly adding allow-lists without fixing the issue.
- Using a scanner that doesn't support your language or framework — e.g., using
pip-auditfor a Node project. - Forgetting to cache dependencies in CI, causing the scan step to time out on large repos.
- Not uploading scan reports as artifacts, so developers have no easy way to review the findings.
Variations
- Use Snyk instead of pip-audit for broader language coverage and a unified dashboard via its GitHub Action.
- Adopt Trivy for container scanning and IaC misconfiguration detection in addition to dependency scans.
- Leverage GitLab CI or Jenkins instead of GitHub Actions — the same principles apply with their native security scanner integrations.
Real-world use cases
- Scanning every pull request in a Python web app to block merges with critical dependency vulnerabilities before release.
- Preventing secret leakage by running gitleaks in CI on all commits across a monorepo, immediately alerting on new secrets.
- Using Trivy to scan Docker images in a Kubernetes deployment pipeline, stopping builds with critical OS-level CVEs from reaching the registry.
Key takeaways
- Security scanning in CI turns security into a continuous, automated gate — every commit is checked, not just periodic audits.
- A practical setup combines dependency (SCA), source (SAST), and secret scanning for comprehensive protection.
- Define failure thresholds based on severity (e.g., fail on critical) to balance security with developer velocity.
- Choose tools based on your stack — Python projects can start with pip-audit, Bandit, and gitleaks.
- CI reports should be visible to developers through artifacts or pull-request comments for efficient remediation.
- Scan results are only useful if they block the pipeline — ensure your CI job fails on violations.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.