CI/CD Pipeline Security Scanning
Integrate security scanning into CI/CD pipelines: SAST, SCA, container scanning, secrets detection, and policies that block vulnerable code before it reaches production.
What you'll learn
- ✓The four types of security scanning and where each fits in a pipeline
- ✓Integrating SAST tools like Semgrep and CodeQL into pull requests
- ✓Using SCA to catch vulnerable dependencies before they ship
- ✓Container image scanning with Trivy and policy enforcement
- ✓Secrets detection to prevent credentials from entering the codebase
Prerequisites
None — this post is self-contained.
Security scanning in CI/CD pipelines catches vulnerabilities before they reach production. The idea is simple: run automated security tools at every pull request and block merges that introduce known risks. In practice, the challenge is integrating multiple types of scanning without making the pipeline so slow or noisy that developers ignore the results. This guide covers the four main scanning types and how to wire them into a practical pipeline.
Types of Security Scanning
Each scanning type targets a different attack surface:
SAST (Static Application Security Testing) analyzes your source code for patterns that indicate vulnerabilities: SQL injection, cross-site scripting, path traversal, and other CWE categories. It runs without executing the code.
SCA (Software Composition Analysis) checks your dependency tree against vulnerability databases like the National Vulnerability Database (NVD) and GitHub Advisory Database. It catches known CVEs in third-party packages.
Container Scanning inspects Docker images for vulnerable OS packages, misconfigured permissions, and known CVEs in the base image layers.
Secrets Detection scans code and commit history for accidentally committed credentials: API keys, passwords, tokens, and private keys.
SAST with Semgrep
Semgrep is a fast, open-source SAST tool that supports custom rules alongside its community rulesets. Add it to a GitHub Actions workflow:
name: Security
on: [pull_request]
jobs:
sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Semgrep
uses: semgrep/semgrep-action@v1
with:
config: >-
p/default
p/owasp-top-ten
p/javascript
generateSarif: true
- name: Upload SARIF
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: semgrep.sarif
The SARIF upload integrates findings directly into GitHub’s Security tab and annotates pull request diffs with inline comments on the affected lines.
For larger codebases, CodeQL provides deeper analysis at the cost of longer scan times:
codeql:
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- uses: actions/checkout@v4
- uses: github/codeql-action/init@v3
with:
languages: javascript, python
- uses: github/codeql-action/analyze@v3
CodeQL builds a database of your code’s data flow and queries it for vulnerability patterns. It catches issues that pattern-based tools miss, like tainted data flowing through multiple function calls.
SCA for Dependency Vulnerabilities
Dependency scanning is the highest-value, lowest-effort security check. Most breaches exploit known CVEs in unpatched dependencies, not zero-days.
Use npm audit or pip audit for language-specific scanning, or a dedicated tool like Trivy for broader coverage:
dependency-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Trivy for dependencies
uses: aquasecurity/trivy-action@master
with:
scan-type: fs
scan-ref: .
severity: HIGH,CRITICAL
exit-code: 1
format: table
Setting exit-code: 1 makes the job fail when high or critical vulnerabilities are found. This blocks the PR from being merged until the dependency is updated or the finding is explicitly suppressed.
Handling False Positives
Not every CVE applies to your usage of a dependency. Create a .trivyignore file to suppress specific findings with justification:
# CVE-2024-12345: Affects XML parsing, which we don't use
CVE-2024-12345
# CVE-2024-67890: Fixed in our fork, pending upstream merge
CVE-2024-67890
Review suppressions periodically. A suppressed CVE that was irrelevant six months ago may become relevant after a code change.
Container Image Scanning
Container scanning goes beyond dependency scanning by checking the OS packages and configuration of your Docker images:
container-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Scan image
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
severity: HIGH,CRITICAL
exit-code: 1
format: sarif
output: trivy-image.sarif
- name: Upload SARIF
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy-image.sarif
Common findings include outdated base images with known CVEs, packages installed by apt-get that have patches available, and running containers as root. Fix these in your Dockerfile:
# Use a specific, regularly updated base image
FROM node:22-slim AS base
# Create a non-root user
RUN groupadd -r app && useradd -r -g app app
# Install dependencies as root, then switch
COPY package*.json ./
RUN npm ci --omit=dev
COPY --chown=app:app . .
USER app
CMD ["node", "server.js"]
Secrets Detection
Secrets in source code are among the most damaging security issues because they provide direct access to production systems. Scan for them before commits land:
secrets-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for scanning commits
- name: Run Gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Gitleaks scans both the current code and the commit history. The full history scan catches secrets that were committed and then deleted, which are still accessible in Git history.
For pre-commit prevention, add Gitleaks as a client-side hook:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
Building a Complete Security Pipeline
Combine all four scanning types into a single workflow that runs on pull requests:
name: Security Pipeline
on:
pull_request:
branches: [main]
jobs:
sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: semgrep/semgrep-action@v1
with:
config: p/default
sca:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: aquasecurity/trivy-action@master
with:
scan-type: fs
severity: HIGH,CRITICAL
exit-code: 1
secrets:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
container:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: docker build -t app:scan .
- uses: aquasecurity/trivy-action@master
with:
image-ref: app:scan
severity: CRITICAL
exit-code: 1
All four jobs run in parallel. The PR cannot be merged until all pass. Set branch protection rules to require these status checks.
Reducing Noise
The biggest risk with security scanning is alert fatigue. If every PR triggers dozens of low-severity findings, developers learn to ignore the results. Manage noise by starting with high and critical severity only, expanding to medium once the backlog is clear. Suppress false positives with documented justification. Send findings to a triage queue rather than blocking PRs for informational findings. And track metrics on mean time to remediate so you know whether the pipeline is actually reducing risk.
Key Takeaways
A CI/CD security pipeline combines SAST for code-level vulnerabilities, SCA for dependency CVEs, container scanning for image issues, and secrets detection for leaked credentials. Run all four in parallel on every pull request. Start with high and critical severity thresholds to avoid alert fatigue, and enforce results through branch protection rules. The goal is catching known vulnerabilities automatically so that security reviews can focus on design-level issues that tools cannot detect.
Related articles
- CI/CD Secrets Management in CI/CD Pipelines
Learn how to securely manage secrets in CI/CD pipelines using environment variables, vault integrations, and automated rotation strategies.
- DevOps DevSecOps Security Scanning in CI/CD Pipelines
Integrate security scanning into your CI/CD pipeline with practical examples covering SAST, SCA, container scanning, and secrets detection.
- CI/CD GitHub Actions vs Jenkins: CI/CD Compared
Compare GitHub Actions and Jenkins for CI/CD pipelines. Covers setup, configuration, plugins, pricing, and when to choose each tool for your team.
- CI/CD CI/CD Artifact Management: Build, Store, and Promote
Learn how to manage build artifacts in CI/CD pipelines including versioning, storage, promotion strategies, and cleanup policies.