On this page
How to Add Security Scanning to Your CI/CD Pipeline
Add secret, dependency, and SAST scanning to a GitHub Actions pipeline. A practical tutorial with working workflow YAML.
Quick answer
- Add three scanners to CI/CD: secrets (Gitleaks), dependencies, and static analysis (Semgrep or CodeQL).
- Each runs on every pull request and fails the build on critical findings.
- The result is that a generated commit shipping a secret or a SQL injection gets caught before merge.
What you’ll build
Security scanning for a GitHub Actions pipeline, layered on top of the CI workflow from the GitHub Actions tutorial. Three checks run on every pull request: secret detection, dependency vulnerability scanning, and static analysis.
Step 1 — Add secret scanning
Add a job that runs Gitleaks:
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 }}How to verify it worked: push a commit containing a fake secret like AKIAIOSFODNN7EXAMPLE, open a pull request, and confirm the secrets job fails with the finding.
Step 2 — Add dependency scanning
For a Node.js project, use npm audit (or a dedicated scanner like Trivy for broader coverage):
deps:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm audit --audit-level=highHow to verify it worked: npm audit exits non-zero when a high-severity vulnerability exists, failing the job.
Step 3 — Add static analysis (SAST)
Add Semgrep, which scans source code for vulnerability patterns:
sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: semgrep/semgrep-action@v1
with:
config: p/defaultHow to verify it worked: introduce a classic flaw (a raw SQL query built with string concatenation) and confirm Semgrep flags it in the pull request.
Step 4 — Decide what blocks a merge
Start by failing on secrets and high-severity dependency issues, and reporting (not blocking) on everything else. Over-blocking produces alert fatigue and skipped checks; under-blocking defeats the point. Revisit the policy as the noise settles.
Where this bites vibecoders
Security scanning is the safety net for AI-generated code: the assistant won’t remember your team’s security rules, but the pipeline enforces them every time. The trap is turning on every scanner at once, seeing a wall of findings, and disabling them. Start with secrets plus one more check, tune the noise, then expand.
Where AI coding assistants get this wrong
- Pasting scanner jobs with action versions or config keys that don’t exist.
- Wiring scans to run only on
main, so problems surface after merge instead of before. - Configuring every check to block, guaranteeing alert fatigue and eventual disablement.
- Scanning but not acting: findings with no triage or fix step are decoration.
Checklist
- Run scans on every pull request, not just on main.
- Fail on secrets and high-severity findings; report the rest.
- Verify each scanner actually catches a planted test issue.
- Triage findings and fix or suppress them deliberately.
- Revisit thresholds as noise drops.
FAQ
What is the difference between SAST and dependency scanning?
SAST analyzes your source code for vulnerability patterns (SQL injection, XSS). Dependency scanning checks the third-party libraries you import for known CVEs. They catch different problems and belong together. See What Is SAST?.
Should scans block merges?
High-confidence, high-impact findings (leaked secrets, critical CVEs) should block. Noisier checks should report first, then block once tuned. A pipeline that blocks on everything gets bypassed; one that blocks on nothing is theater.
Is Gitleaks the only secret scanner?
No. Alternatives include TruffleHog and the built-in GitHub secret scanning. Gitleaks is a common choice because it’s open source and works locally and in CI.
Related topics
- What Is DevSecOps?
- How to Scan Your Codebase for Hardcoded Secrets
- What Is SAST?
- How to Add SAST Scanning to a GitHub Repo