Integrating Application Security into CI/CD Without Slowing Down Deploys
“Shift left” has become one of those phrases that means everything and nothing. Every vendor says it. Every conference talk mentions it. But when you actually try to add security scanning to a CI/CD pipeline, you run into the real question: how do you do this without making every pull request take 45 minutes and generating 300 findings that nobody looks at?
We’ve integrated security tooling into pipelines at companies ranging from 5-person startups to 500-engineer organizations. Here’s what actually works.
The Three Tools You Need First
You don’t need an expensive enterprise application security platform to start. You need three categories of tooling, and good open-source or low-cost options exist for each.
Static Application Security Testing (SAST). This scans your source code for vulnerability patterns. Think SQL injection, cross-site scripting, insecure deserialization. Our recommendation: Semgrep. It’s fast, has excellent default rules, and lets you write custom rules in a syntax that developers can actually understand. The community ruleset covers OWASP Top 10 patterns across most popular languages. For most teams, Semgrep’s free tier is enough to start.
Software Composition Analysis (SCA). This scans your dependencies for known vulnerabilities. You’re not just shipping your code. You’re shipping every npm package, Python library, and Go module you import. Our recommendation: Snyk or Grype. Snyk has better remediation guidance and a polished UI. Grype is fully open source and integrates well with container scanning. If you’re already using GitHub, Dependabot covers the basics but lacks the depth of dedicated SCA tools.
Secret Scanning. This catches credentials, API keys, and tokens that get committed to source code. This happens constantly, even at disciplined organizations. Our recommendation: Gitleaks for pre-commit hooks and CI scanning, or TruffleHog for deeper historical scanning. GitHub’s built-in secret scanning is decent if you’re on GitHub Enterprise, but it only covers known provider patterns.
That’s it. Three tools. Each one addresses a distinct and critical category of risk.
Where to Run Each Scan
This is where most teams get it wrong. They add all three scans to every pull request and wonder why developers are frustrated.
SAST (Semgrep): Run on every pull request, scoped to changed files.
Semgrep is fast enough to run on PRs without meaningful delay. The key is to scope it to changed files only. Don’t scan the entire codebase on every PR. In GitHub Actions, this looks like using dorny/paths-filter or Semgrep’s built-in diff-aware mode (semgrep ci with baseline comparison). A scan of changed files typically takes 10-30 seconds.
SCA (Snyk/Grype): Run on dependency file changes and on a nightly schedule.
SCA scans don’t need to run on every PR. Trigger them when package-lock.json, requirements.txt, go.sum, or equivalent files change. Then run a full scan nightly against your main branch. New CVEs are published daily, and a dependency that was clean yesterday might have a critical vulnerability today. The nightly scan catches those.
Secret scanning (Gitleaks): Run on every pull request and as a pre-commit hook. This is the one scan that should block every commit and every PR without exception. Leaked secrets are immediately exploitable. A 5-second scan that catches an AWS key before it hits your remote repo is worth any amount of pipeline time. Install Gitleaks as a pre-commit hook for immediate developer feedback, and run it again in CI as a safety net.
Gate vs. Notify: The Decision That Matters Most
For each scan, you need to decide: does a finding block the PR, or does it just create a notification?
Our strong opinion: start with notify, not gate.
If you start by blocking PRs on every finding, two things happen. First, developers spend hours triaging false positives instead of shipping features. Second, they start resenting the security tooling and finding workarounds (inline suppression comments everywhere, or worse, bypassing CI entirely).
Here’s the progression we recommend:
Week 1-4: Notify only. Run all three scans and post findings as PR comments, but don’t block merges. Use this period to measure your false positive rate and tune your rules.
Week 4-8: Gate on critical/high findings only. After tuning, start blocking PRs that have critical or high-severity findings from SAST and SCA. Keep medium and low as notifications. Keep secret scanning as a hard gate from day one (there’s no such thing as a “low severity” leaked credential).
Week 8+: Refine and expand. Tighten your gates as the team builds confidence. Add custom Semgrep rules for patterns specific to your codebase. Expand SCA scanning to cover container images if you’re using Docker.
The gradual approach builds trust. Developers see the tooling catch real issues before they see it blocking their work.
Tuning for Signal Over Noise
The number one killer of security tooling adoption is noise. If 80% of your findings are false positives, developers will stop looking at any of them.
Semgrep tuning. Start with the p/default ruleset, not p/security-audit. The default ruleset is curated for high-confidence findings. The audit ruleset casts a much wider net and will generate noise. After a few weeks, review the findings that were ignored or dismissed. Create a .semgrepignore file for patterns that don’t apply to your codebase. Write custom rules for patterns that are specific to your application.
SCA tuning. Not every CVE in your dependency tree is exploitable in your context. A vulnerability in a function you never call is a low priority. Snyk’s reachability analysis helps with this by identifying whether the vulnerable code path is actually reachable from your application. If you’re using Grype, you’ll need to do this triage manually. Create an exceptions file for CVEs you’ve reviewed and accepted.
Gitleaks tuning. The default rules include patterns for common secrets (AWS keys, GitHub tokens, generic passwords). You’ll likely need to add allowlist entries for test fixtures, example configs, and false positives from strings that happen to match key patterns. The .gitleaksignore file is your friend.
Spend time on tuning. It’s not glamorous work, but the difference between a 90% false positive rate and a 10% false positive rate is the difference between a tool that gets ripped out in three months and a tool that becomes part of your development culture.
Dealing with Developer Pushback
You will get pushback. That’s normal. Here’s how to handle the most common objections.
“This is slowing down my deploys.” If your security scans add more than 2 minutes to a PR pipeline, something is wrong. Scope SAST to changed files. Run SCA only on dependency changes. Parallelize the scans. A well-configured security stage should add 30-90 seconds to your pipeline.
“This finding is a false positive.” Make it easy to dismiss false positives with a clear process. In Semgrep, a # nosemgrep: rule-id comment with a brief justification is fine. Track suppressions and review them periodically to make sure they’re legitimate.
“I don’t have time to fix this.” Create a security debt backlog. Not every finding needs to be fixed before merging. Critical and high findings should gate the PR. Medium and low findings should be tracked as backlog items with SLAs. We typically recommend 30 days for medium, 90 days for low. Track compliance against those SLAs.
“Security should be someone else’s problem.” This is a culture issue, not a tooling issue. The most effective approach we’ve seen is having a security champion in each development team. This is a developer who spends 10-20% of their time on security, triages findings for their team, and acts as the liaison between security and engineering. They’re not a dedicated security person. They’re a developer with security context.
A Sample GitHub Actions Pipeline
Here’s a concrete example of what this looks like in GitHub Actions:
Run SAST, SCA, and secret scanning as parallel jobs in a security stage. SAST uses semgrep ci with SEMGREP_BASELINE_REF set to the PR’s base branch. SCA runs snyk test only when dependency files change (use paths filter on the job). Secret scanning runs gitleaks detect --source=. --log-opts="$BASE_REF..HEAD" to scan only new commits.
Each job posts results as PR comments using their respective GitHub integrations. SAST and SCA findings at critical/high severity set the job to failure, which blocks the merge if you’ve configured branch protection. Secret scanning always sets the job to failure on any finding.
Total added pipeline time: 60-90 seconds when all three run in parallel.
What “Shift Left” Actually Looks Like
“Shift left” doesn’t mean “dump security responsibility on developers and walk away.” It means giving developers the tools and context to catch security issues early, while they still have the mental context to fix them quickly.
A developer who sees a SQL injection finding in their PR can fix it in five minutes. The same finding discovered in a penetration test three months later takes a week to fix because nobody remembers how that code works.
That’s the real value proposition. Not compliance. Not checking a box. Faster, cheaper fixes.
If you’re trying to add security to your pipeline and want help getting it right the first time, we do this regularly. We’ll set up the tooling, tune it for your codebase, and train your team on the workflow. Most engagements take two to three weeks to get fully operational.
Need help with this?
We place senior security engineers with teams like yours. Tell us what you're working on.
Get in Touch