Security Blog

How to Set Up Continuous Security Testing in Your CI/CD Pipeline

A step-by-step guide to adding security gates to your GitHub Actions workflow — without blocking deploys on false positives.

cicdgithub actionsdevsecopssecurity testing

Most CI/CD security setups fail in one of two ways: they block the pipeline so aggressively on false positives that developers learn to route around them, or they're so permissive they catch nothing real. The job is threading that needle — gates that fire on genuine risk and stay silent otherwise. Here's how to build them.

The three layers of CI/CD security

Layer 1: Pre-commit (developer workstation)

The fastest feedback loop. Runs before code even leaves the developer's machine.

  • Secret scanning via gitleaks pre-commit hook
  • Linting and basic static analysis

Setup with pre-commit framework:

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

Layer 2: PR check (before merge)

This is where substantive security review happens. The code exists as a complete diff, there's enough context for semantic analysis, and the change hasn't merged yet — so findings can block a merge or require human review.

What to run at PR time:

  • Dependency CVE scanning — check every new or updated dependency against OSV
  • Secret scanning — scan the PR's commit history for leaked credentials
  • AI security review — semantic analysis of the diff for injection, auth flaws, config issues
  • SAST — pattern-based static analysis (lower signal, but fast)

BattleTest handles the first three automatically when you install the GitHub App — no GitHub Actions configuration needed. Results post to the PR as a comment within minutes.

Layer 3: Post-deploy (running infrastructure)

Once the code is deployed, dynamic testing against the running application catches what static analysis misses:

  • Endpoint discovery (what's actually reachable from the internet)
  • Active vulnerability probing (SQLi, XSS, SSRF, misconfigurations)
  • Regression checking (did this deploy break something that was passing before?)

GitHub Actions integration

For teams running additional security checks in CI, here's a minimal setup that doesn't block on false positives:

name: Security
on:
  pull_request:
    branches: [main]

jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # full history for secret scanning

      - name: Secret scan
        uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}  # Only needed for gitleaks commercial licence

      - name: Dependency audit
        run: npm audit --audit-level=high
        continue-on-error: true  # report don't block

      - name: Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          severity: 'CRITICAL'
          exit-code: '1'  # only fail on CRITICAL

The key decision: block vs report

The biggest mistake teams make is blocking the pipeline on findings with high false positive rates. A scan that produces 10 false positives per PR trains developers to ignore the results — and then you miss the real one.

A better model:

  • Hard block: Verified secrets (confirmed valid credentials found in the diff)
  • Soft block: CRITICAL severity CVEs in direct dependencies
  • Required review: HIGH findings from semantic analysis
  • Advisory: MEDIUM and LOW findings, config suggestions

BattleTest posts findings as a PR comment by default — it doesn't fail your CI check. The security review is advisory unless you configure required status checks in GitHub.

Don't let security block velocity

The goal is not to prevent every deployment until all findings are resolved. The goal is to ensure every finding is visible, tracked, and resolved before it becomes a breach. Those are different things.

A finding that gets acknowledged and triaged is better than a finding that blocks the pipeline and gets bypassed with --no-verify.

Try BattleTest

Catch this class of bug before it ships.

BattleTest reviews every PR for injection flaws, leaked secrets, and CVEs — then battle-tests your live infrastructure the way an attacker would.

Connect your GitHub repository free →

Free for public repos · Private repos from $20/mo · No credit card required