Security Scanning in CI

Add security scanning to your CI pipeline: tools, setup, and best practices.

Focus: integrate security scanning into ci

Sponsored

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:

  1. Choose your scanner(s) — Pick tools that match your tech stack. For Python, pip-audit scans PyPI dependencies; for containers, Trivy; for general source, Bandit.
  2. Configure the scanner — Define what to scan, which severity to fail on, and where to output the report (JSON, SARIF).
  3. 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.
  4. Set up failure thresholds — Decide the policy: fail if any CRITICAL vulnerabilities found, or fail if HIGH or above.
  5. Upload reports and notifications — Make the results visible to developers (e.g., SARIF upload to GitHub) and send alerts on Slack.
  6. 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-hashes to 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-audit only works with Python. Find alternatives like npm audit for Node or Trivy for any OS/package.
  • Secret scanner flags test secrets: Use a .gitleaks.toml config 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-upload or 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, and gitleaks.
  • 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-audit for 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

  1. Use Snyk instead of pip-audit for broader language coverage and a unified dashboard via its GitHub Action.
  2. Adopt Trivy for container scanning and IaC misconfiguration detection in addition to dependency scans.
  3. 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.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.