How to integrate security into CI/CD without slowing your team down
Shift left doesn't mean shift slow.
Iago Mussel
CEO & Founder
“We’ll add security later” is how you end up with a CVE in production, a compliance audit you failed, or a compromised dependency you didn’t know existed.
But I get the pushback. Engineers have seen security processes that added 20 minutes to every build, blocked deploys over low-severity findings that had been “known and accepted” for months, and required three approvals for a CSS change. That’s not security. That’s theater with a badge on it.
The goal is security that actually catches real problems — without turning your pipeline into a parking lot.
The principle: only block on what actually matters
Security checks should run early, run automatically, and hard-block only on findings that represent real risk.
Everything else — informational findings, accepted risks, known false positives — goes into a report reviewed on a cadence. Not a gate that stops shipping.
The teams that hate security in CI/CD are the ones running noisy scanners sequentially, getting blocked by the same stale finding every deploy, and spending more time managing the security tool than the security problem.
Fix the noise. The signal is worth protecting.
Layer 1: Secret scanning — non-negotiable, runs everywhere
A leaked API key or database credential in your repository is an incident. This is the highest-severity risk with the simplest fix.
Run secret scanning on every commit. It’s fast and the false positive rate is manageable with the right configuration.
- name: Secret scanning
uses: trufflesecurity/trufflehog@main
with:
path: ./
base: ${{ github.event.repository.default_branch }}
head: HEAD
extra_args: --only-verified
--only-verified filters to credentials that are confirmed active. This eliminates the majority of false positives from test fixture data and example configs.
If a secret is found: rotate it immediately, then fix the commit. That order matters. Rotate first.
Layer 2: Dependency scanning
Your application is mostly other people’s code. That’s fine — until one of those libraries has a known vulnerability and you’re carrying it to production unaware.
- name: Dependency audit
run: npm audit --audit-level=high
Run this in every PR. Block on CRITICAL and HIGH. Report MODERATE and LOW for scheduled review.
For container images, add scanning before push:
- name: Scan image
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.IMAGE_NAME }}:${{ github.sha }}
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
exit-code: '1'
My defaults: CRITICAL always blocks. HIGH blocks with an exception process. MEDIUM and LOW go to a scheduled review. These aren’t magic thresholds — adjust for your risk tolerance — but having a documented policy matters more than the specific levels.
Layer 3: Static analysis (SAST)
SAST tools scan your code for security patterns: SQL injection, XSS, unsafe deserialization, hardcoded credentials. They catch things that linters miss.
GitHub’s CodeQL is free for open source:
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: javascript
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:javascript"
SAST has a false positive problem. Every tool does. Tune the ruleset to your language and framework, and start in report-only mode before enabling hard blocking. If you turn on hard blocking before you’ve reviewed the initial findings, you’ll immediately have a backlog of blocked deploys and a team that resents the tool.
Layer 4: Infrastructure scanning
If you’re shipping Terraform or Kubernetes manifests, scan them.
- name: Scan Terraform
uses: aquasecurity/[email protected]
with:
soft_fail: true
Start with soft_fail: true. Set a date — two or three sprints out — to move it to hard fail after you’ve worked through the initial findings. This is the same “report first, block second” pattern. It works.
Run all of this in parallel
The single biggest reason security in CI/CD is slow is sequential execution.
jobs:
security:
runs-on: ubuntu-latest
strategy:
matrix:
check: [secrets, dependencies, sast]
steps:
- uses: actions/checkout@v4
- name: Run ${{ matrix.check }}
run: ./scripts/security/${{ matrix.check }}.sh
Secret scanning, dependency scanning, and SAST have no dependencies on each other. Run them in parallel. A 12-minute sequential security stage becomes a 4-minute parallel one. That’s the difference between “this adds friction” and “this is invisible.”
What to do with findings you can’t fix immediately
Not every finding is fixable now. Some require library upgrades that break compatibility. Some are in code you don’t own. Some are accepted risks for your specific threat model.
These need a documented exception — not ignored, not silently suppressed.
A finding accepted as a known risk needs: who accepted it, why, when it’ll be reviewed again, and what mitigations are in place. A YAML file in the repo reviewed quarterly is enough for most teams. The goal is auditability, not paperwork.
The three things that kill security in pipelines
Running everything on every commit. SAST on a one-line doc change is overhead with no return. Scope checks to what changed.
Hard-blocking on findings you haven’t reviewed. Turn new scanners on in report mode first. See what they catch. Decide what’s signal. Then block on it.
Making it someone else’s problem. When findings get routed to a separate security team, they accumulate. When the engineer who wrote the code sees the finding immediately, it gets fixed. Security in the pipeline only works when the people who wrote the code own the findings.
I work with teams building production systems and developer tooling. If this topic resonates, you can find more of my work at https://huntermussel.com.
Share
Related articles
How a modern CI/CD pipeline works — and where AI actually fits
A clear breakdown of modern CI/CD pipelines, what each stage does, and where AI is genuinely useful versus just hype.
The problem with migrating from Jenkins to GitHub Actions in the middle of critical systems
Migrating CI/CD for systems that can't afford downtime or broken deploys requires more than rewriting Jenkinsfiles. Here's the strategy that works.
Jenkins or GitHub Actions? How to choose the right CI/CD tool for your context
An honest comparison of Jenkins and GitHub Actions — including when Jenkins is still the right answer and when it's technical debt you don't need.