Shifting Security Left: How to Add Vulnerability Scanning to Your CI/CD Pipeline

Profile
Yves SoeteFollow
6 min read · Mar 2, 2026

MAR 2, 2026- Written by Yves SoeteBlacksight LLC visit us to use our free website security scanner onscanner.blacksight.io

Get notified when new articles drop — visitblacksight.io/blog to subscribe.

The traditional approach to application security is to build first and scan later. Developers write code for weeks or months, then a security team runs an assessment, produces a 200-page report, and throws it over the wall. By the time vulnerabilities are found, the code is in production, the developers have moved on to other features, and fixing anything requires context-switching back to code they barely remember writing.

Shifting security left means integrating security checks into every stage of your development pipeline — from the moment code is written to the moment it reaches production. The earlier you catch a vulnerability, the cheaper and faster it is to fix. A SQL injection caught in a pre-commit hook costs five minutes to remediate. The same vulnerability caught in production costs days of incident response, potential data exposure, and customer trust.



1. The Shift-Left Philosophy

Shift-left is not about adding more tools. It is about changing when and where security decisions happen. In a traditional pipeline, security is a gate at the end — a final checkpoint before release. In a shift-left pipeline, security is woven into every stage: code, commit, build, test, deploy, and monitor.

The goal is not to eliminate dedicated security reviews. You still need penetration testing, threat modeling, and architecture reviews. The goal is to automate the repeatable, well-understood checks so that the security team can focus on the hard problems that require human judgment. If your security engineers are spending their time finding missing security headers and outdated dependencies, they are not spending their time on business logic vulnerabilities and design flaws.

Every pipeline stage has different security checks that make sense. Pre-commit catches secrets before they enter the repository. PR checks catch vulnerable dependencies and static analysis findings before code merges. Build stage catches container vulnerabilities and dynamic issues. Post-deploy monitoring catches runtime problems and configuration drift. Let me walk through each stage.



2. Pre-Commit: Catching Secrets Before They Enter the Repository

The cheapest place to catch a security issue is before it ever leaves the developer's machine. Pre-commit hooks run automatically before each commit and can block commits that contain sensitive data.

GitLeaks is the go-to tool here. It scans staged changes for patterns that match API keys, passwords, tokens, private keys, and other secrets. Install it as a pre-commit hook and it will prevent developers from accidentally committing credentials:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.18.0
    hooks:
      - id: gitleaks


The key to successful pre-commit hooks is speed. If a hook takes more than a few seconds, developers will bypass it. GitLeaks runs in milliseconds because it only scans staged changes, not the entire repository. Pair it with a .gitleaksignore file for known false positives so developers are not constantly overriding the hook for legitimate patterns.

Beyond secrets detection, consider adding hooks for file size limits (preventing accidental binary commits), merge conflict markers (preventing broken merges from being committed), and basic formatting checks. Keep the total hook execution time under three seconds.



3. PR Checks: SAST and Dependency Scanning

When a developer opens a pull request, two categories of automated checks should run: static application security testing (SAST) and dependency scanning.

SAST tools analyze source code without executing it, looking for patterns that indicate vulnerabilities — SQL injection, cross-site scripting, insecure deserialization, hard-coded secrets, and more. Semgrep is an excellent open-source option that supports dozens of languages and allows you to write custom rules. It runs fast enough to be practical in CI and produces findings with clear explanations and fix suggestions.

Dependency scanning checks your project's dependencies against known vulnerability databases. Dependabot (built into GitHub) and Snyk are the most popular options. They monitor your lock files (package-lock.json, Gemfile.lock, mix.lock, requirements.txt) and alert you when a dependency has a known CVE. Here is a GitHub Actions workflow that runs both:

# .github/workflows/security.yml
name: Security Checks
on: [pull_request]
jobs:
  sast:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: returntocorp/semgrep-action@v1
        with:
          config: p/default p/owasp-top-ten
  deps:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run Snyk
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}


The critical design decision is whether these checks are blocking (must pass to merge) or advisory (report findings but allow merge). Start advisory to avoid disrupting the team, then move to blocking once false positives are tuned out and the team is comfortable with the process.



4. Build Stage: Container Scanning and DAST

Once code is merged and built, new security checks become relevant. If you deploy containers, you need to scan your images for vulnerabilities in base images and installed packages. Trivy is the best open-source tool for this — it scans container images, filesystems, and even IaC configurations in one tool.

Run Trivy against every container image before it is pushed to your registry:

# In your CI pipeline
trivy image --severity HIGH,CRITICAL --exit-code 1 myapp:latest


The --exit-code 1 flag makes Trivy return a non-zero exit code when high or critical vulnerabilities are found, which fails the pipeline. This is your quality gate — no image with known critical vulnerabilities reaches production.

Dynamic application security testing (DAST) runs against a deployed instance of your application, testing it the way an attacker would — by sending requests and analyzing responses. OWASP ZAP is the standard open-source DAST tool. Deploy your application to a staging environment during the build pipeline and run ZAP against it. ZAP's baseline scan is fast enough for CI and catches common issues like missing headers, insecure cookies, and reflected XSS.



5. GitLab CI: An Alternative Pipeline Example

If you use GitLab instead of GitHub, the approach is the same but the syntax differs. GitLab has built-in security scanning templates that you can include directly:

# .gitlab-ci.yml
include:
  - template: Security/SAST.gitlab-ci.yml
  - template: Security/Dependency-Scanning.gitlab-ci.yml
  - template: Security/Container-Scanning.gitlab-ci.yml
  - template: Security/Secret-Detection.gitlab-ci.yml

stages:
  - test
  - security
  - deploy

container_scanning:
  variables:
    CS_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA


GitLab's security dashboard aggregates findings across all these scanners into a single view, which is convenient for tracking and triaging. The paid tiers include vulnerability management features like dismissing false positives, tracking remediation, and enforcing approval rules based on vulnerability severity.

Regardless of whether you use GitHub or GitLab, the principle is the same: define your security checks as code, run them automatically, and make the results visible to the team.



6. Quality Gates: When to Break the Build

The hardest question in pipeline security is: what should block a deployment? Break the build too aggressively and developers lose trust in the system. Too lenient and the security checks become background noise that nobody reads.

Here is the approach I recommend. Define three tiers of findings: blockers that must be fixed before merge (critical CVEs in dependencies, leaked secrets, SQL injection), warnings that should be fixed soon but do not block (medium-severity findings, missing security headers), and informational findings that are logged but require no immediate action (low-severity issues, style warnings).

Only blockers should prevent a PR from merging. Warnings should be visible in the PR review but not block it. Informational findings should be aggregated in a dashboard for periodic review. This tiered approach maintains developer velocity while ensuring critical issues never reach production.

Review your quality gate thresholds quarterly. If a particular rule generates more false positives than true findings, tune it or demote it. If a warning-level finding leads to an actual incident, promote it to blocker. The thresholds should evolve based on real data from your pipeline.



7. Managing False Positives

False positives are the number one reason security scanning in CI/CD fails. If developers see five irrelevant findings on every PR, they stop reading the results entirely. Managing false positives is not optional — it is essential to the system's credibility.

Every tool has a mechanism for suppressing false positives. Semgrep uses nosemgrep comments. Trivy uses .trivyignore files. Snyk uses .snyk policy files. Use these mechanisms liberally, but with documentation. Every suppression should include a comment explaining why the finding is a false positive, who reviewed it, and when the suppression should be revisited.

Maintain a central suppression list that is reviewed monthly. Suppressions are not permanent — they should expire and be re-evaluated. A finding that was a false positive six months ago might be relevant today if your code or dependencies have changed. Track your false positive rate as a metric. If it exceeds 20%, your tool configuration needs attention.



8. Developer Experience: Making Security Checks Fast and Actionable

The ultimate success metric for pipeline security is whether developers actually fix the findings. If checks are slow, noisy, or confusing, developers route around them. If they are fast, accurate, and clear, they become a natural part of the workflow.

Speed matters. Security checks should complete in minutes, not hours. Run scanners in parallel where possible. Use incremental scanning (only scan changed files) for SAST. Cache vulnerability databases locally so scanners do not download them on every run. If a check takes more than ten minutes, developers will stop waiting for it and merge based on other signals.

Actionability matters. A finding that says "potential XSS in file.js line 42" is marginally useful. A finding that says "user input from req.query.name is passed unsanitized to res.send() on line 42, use escapeHtml() to fix" is immediately actionable. Choose tools that provide clear remediation guidance, not just vulnerability descriptions.

Integration matters. Findings should appear where developers already work — as PR comments, IDE warnings, or Slack notifications. Do not ask developers to log into a separate security dashboard to review findings. Meet them in their existing workflow. Most CI security tools support PR annotations that place findings directly on the relevant lines of changed code.

Shifting security left is a cultural change as much as a technical one. Start small — add one check to one pipeline and measure the impact. Expand gradually as the team builds confidence. Within a quarter, you will have a pipeline that catches vulnerabilities automatically, and your security team will have more time for the work that actually requires human expertise. Combine pipeline scanning with external vulnerability monitoring through a tool like BlackSight at scanner.blacksight.io to cover both the code you write and the infrastructure you deploy it to.

Bonus: Use our free website vulnerability scanner at scanner.blacksight.io

Liked this article? Get notified when new articles drop! visitblacksight.io/blog to subscribe

Version 1.0.49