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.
What you'll learn
- ✓The four layers of DevSecOps scanning
- ✓How to add SAST and SCA to a GitHub Actions pipeline
- ✓Container image vulnerability scanning with Trivy
- ✓Secrets detection to prevent credential leaks
- ✓How to avoid alert fatigue from security tools
Prerequisites
None — this post is self-contained.
Security scanning bolted on at the end of a release cycle finds problems too late to fix cheaply. DevSecOps moves scanning into the CI/CD pipeline so that vulnerabilities surface when the code is still fresh in the developer’s mind. This article walks through four layers of automated security scanning and shows how to wire each one into a practical pipeline.
The Four Layers
A mature DevSecOps pipeline scans at four levels:
- SAST (Static Application Security Testing) — analyzes source code for vulnerabilities without running it. Catches SQL injection patterns, insecure deserialization, hardcoded credentials, and similar flaws.
- SCA (Software Composition Analysis) — checks your dependencies against known vulnerability databases. The Log4Shell incident proved that your code is only as secure as your dependency tree.
- Container image scanning — inspects the OS packages and application layers in your Docker images for CVEs.
- Secrets detection — scans commits and files for accidentally committed API keys, tokens, passwords, and private keys.
SAST with Semgrep
Semgrep runs pattern-based static analysis across many languages. Add it to a GitHub Actions workflow:
name: Security Scans
on:
pull_request:
branches: [main]
jobs:
sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Semgrep
uses: semgrep/semgrep-action@v1
with:
config: >-
p/owasp-top-ten
p/nodejs
p/python
env:
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
The p/owasp-top-ten ruleset catches the most common web application vulnerabilities. Language-specific rulesets add framework-aware checks. Semgrep is fast because it works on AST patterns rather than full program analysis, so it fits naturally into PR checks without slowing developers down.
Write custom rules for patterns specific to your codebase:
rules:
- id: no-eval-user-input
patterns:
- pattern: eval($USER_INPUT)
- pattern-not: eval("constant_string")
message: "Do not pass user input to eval()"
severity: ERROR
languages: [python]
SCA with Dependabot and OSV-Scanner
GitHub Dependabot handles dependency updates automatically, but you should also scan in CI to block PRs that introduce vulnerable packages:
sca:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run OSV-Scanner
uses: google/osv-scanner-action/osv-scanner-action@v1
with:
scan-args: |-
--lockfile=package-lock.json
--lockfile=requirements.txt
--format=table
OSV-Scanner checks lockfiles against the Open Source Vulnerabilities database. It supports npm, pip, Go modules, Maven, and most other ecosystems. Pin it to run on every PR so that a new dependency with a known CVE cannot merge unnoticed.
Container Image Scanning with Trivy
Trivy scans Docker images for OS and application vulnerabilities. Run it after the image build step:
container-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Run Trivy
uses: aquasecurity/trivy-action@0.28.0
with:
image-ref: myapp:${{ github.sha }}
format: table
exit-code: 1
severity: CRITICAL,HIGH
ignore-unfixed: true
Setting exit-code: 1 fails the pipeline if critical or high vulnerabilities are found. The ignore-unfixed flag suppresses noise from CVEs that have no available patch yet, which prevents alert fatigue from issues you cannot act on.
For a stricter policy, generate a Software Bill of Materials (SBOM) and store it as a build artifact:
trivy image --format cyclonedx \
--output sbom.json myapp:latest
Secrets Detection with Gitleaks
Gitleaks scans your git history and staged changes for secrets:
secrets:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run Gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Gitleaks catches AWS keys, GCP service account JSON, private RSA keys, database connection strings, and dozens of other patterns. Run it with fetch-depth: 0 so it scans the full commit history, not just the latest commit.
Add a .gitleaks.toml to suppress false positives:
[allowlist]
description = "Known safe patterns"
paths = [
'''test/fixtures/.*''',
'''docs/examples/.*''',
]
For pre-commit protection, install the Gitleaks pre-commit hook so secrets never reach the remote repository:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.21.0
hooks:
- id: gitleaks
Managing Alert Fatigue
The biggest risk with security scanning is not missing vulnerabilities. It is generating so many alerts that developers ignore them all. A few strategies:
Severity gates. Only block the pipeline on critical and high findings. Log medium and low findings to a tracking system for scheduled triage.
Baseline suppression. When you first enable scanning on a mature codebase, you will get hundreds of findings. Baseline them, fix them in batches, and only alert on new findings in PRs.
Ownership. Route findings to the team that owns the code. A generic security channel that nobody watches is the same as no alerting.
Fix windows. Give teams a defined SLA: critical findings must be fixed within 48 hours, high within a sprint, medium within a quarter. This prevents the backlog from becoming infinite.
Putting It Together
A complete security scanning job in your CI pipeline looks like this sequence:
- Secrets detection runs first because it is fastest and catches the most embarrassing mistakes.
- SAST runs on the source code while the Docker image builds in parallel.
- SCA checks dependency lockfiles.
- Container scanning runs after the image build completes.
Each layer catches a different category of vulnerability. None of them is sufficient alone. Together they form a safety net that catches most issues before they reach production.
Wrap-up
DevSecOps is not a product you buy. It is the practice of embedding security checks into the workflow developers already use. Start with secrets detection and one SAST tool, add SCA and container scanning as you mature, and invest as much effort in reducing false positives as you do in catching true ones. The goal is a pipeline that developers trust, not one they learn to ignore.
Related articles
- DevOps Container Image Optimization Techniques
Reduce Docker image size and build time with multi-stage builds, layer caching, distroless bases, and practical Dockerfile patterns.
- CI/CD CI/CD Secrets Management Best Practices
Keep API keys, tokens, and database credentials safe in CI/CD with rotation, scoping, secret managers, and OIDC-based authentication.
- DevOps Container Orchestration: Docker Swarm vs Kubernetes
Compare Docker Swarm and Kubernetes for container orchestration. Learn the architecture, setup, scaling, and networking of both platforms with practical examples.
- DevOps Blue-Green vs Canary Deployments Explained
Compare blue-green and canary deployment strategies with practical examples to choose the right zero-downtime release approach for your team.