Cloud6 Min Read

Building a Security Pipeline — DevSecOps in Practice

Gorav Singal

April 04, 2026

TL;DR

Shift security left: run SAST (Semgrep) and secret scanning (Gitleaks) on every PR, SCA (Trivy) on every build, DAST (ZAP) in staging, and infra scanning (Checkov) on every Terraform change. Gate on critical findings only — don't block on noise.

Building a Security Pipeline — DevSecOps in Practice

Security tools that nobody runs are security theater. I’ve seen teams buy expensive SAST licenses that sit unused because the tool takes 45 minutes to scan and nobody wants to wait. The secret to a security pipeline that actually works: make it fast, make it automatic, and make it block only what matters.

This article walks through building a complete security pipeline from pre-commit hooks to runtime monitoring.

Why a Security Pipeline?

The economics are simple: fixing a vulnerability in production costs 100x more than fixing it in development. A security pipeline shifts detection left — catching issues when they’re cheapest to fix.

Security Pipeline Stages

But here’s the nuance most teams miss: a security pipeline that blocks every PR on every finding is worse than no pipeline at all. Developers will route around it. The goal is high-signal, low-friction security gates.

The Security Scanning Stack

Every application needs five types of security scanning:

Scan Type What It Finds When to Run Tool
SAST Code-level bugs (SQLi, XSS, hardcoded secrets) Every PR Semgrep
SCA Vulnerable dependencies Every build Trivy, Snyk
Secret Scanning Leaked API keys, passwords, tokens Pre-commit + PR Gitleaks
IaC Scanning Misconfigured infrastructure Every Terraform PR Checkov
DAST Runtime vulnerabilities Staging deploys OWASP ZAP

Security Tool Comparison Matrix

Integrating into CI/CD — The Full Pipeline

Here’s a complete GitHub Actions workflow that implements all five scan types:

# .github/workflows/security-pipeline.yml
name: Security Pipeline

on:
  pull_request:
    branches: [main]
  push:
    branches: [main]

jobs:
  # Stage 1: Fast scans on every PR (< 2 min)
  secret-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Gitleaks Secret Scan
        uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  sast:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Semgrep SAST Scan
        uses: returntocorp/semgrep-action@v1
        with:
          config: >-
            p/security-audit
            p/owasp-top-ten
            p/nodejs
          generateSarif: true

      - name: Upload SARIF
        if: always()
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: semgrep.sarif

  iac-scan:
    runs-on: ubuntu-latest
    if: contains(github.event.pull_request.changed_files, 'terraform/')
    steps:
      - uses: actions/checkout@v4

      - name: Checkov IaC Scan
        uses: bridgecrewio/checkov-action@v12
        with:
          directory: terraform/
          framework: terraform
          soft_fail: false
          skip_check: CKV_AWS_18  # Skip specific checks if needed

  # Stage 2: Build-time scans (< 5 min)
  sca:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Trivy Dependency Scan
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          scan-ref: '.'
          severity: 'CRITICAL,HIGH'
          exit-code: '1'
          format: 'sarif'
          output: 'trivy-results.sarif'

      - name: Upload Trivy SARIF
        if: always()
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: trivy-results.sarif

  image-scan:
    runs-on: ubuntu-latest
    needs: [secret-scan, sast]
    steps:
      - uses: actions/checkout@v4

      - name: Build Docker Image
        run: docker build -t app:${{ github.sha }} .

      - name: Trivy Image Scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: 'app:${{ github.sha }}'
          severity: 'CRITICAL,HIGH'
          exit-code: '1'

  # Stage 3: DAST on staging (post-deploy)
  dast:
    runs-on: ubuntu-latest
    needs: [image-scan]
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Wait for staging deploy
        run: sleep 30  # Wait for deployment to complete

      - name: OWASP ZAP Baseline Scan
        uses: zaproxy/action-[email protected]
        with:
          target: 'https://staging.example.com'
          rules_file_name: '.zap/rules.tsv'
          cmd_options: '-a'

  # Quality Gate — aggregate results
  security-gate:
    runs-on: ubuntu-latest
    needs: [secret-scan, sast, sca, iac-scan]
    if: always()
    steps:
      - name: Check scan results
        run: |
          if [ "${{ needs.secret-scan.result }}" == "failure" ]; then
            echo "❌ Secret scan failed — blocking merge"
            exit 1
          fi
          if [ "${{ needs.sast.result }}" == "failure" ]; then
            echo "⚠️ SAST findings detected — review required"
            # Don't block, but require review
          fi
          if [ "${{ needs.sca.result }}" == "failure" ]; then
            echo "❌ Critical/High vulnerabilities found — blocking merge"
            exit 1
          fi
          echo "✅ Security gate passed"

Tool Deep Dives

Semgrep — SAST

Semgrep is my go-to for SAST because it’s fast (seconds, not minutes) and the rules are readable:

# .semgrep/custom-rules.yml
rules:
  - id: no-hardcoded-aws-keys
    patterns:
      - pattern-regex: "AKIA[0-9A-Z]{16}"
    message: "Hardcoded AWS access key detected"
    languages: [generic]
    severity: ERROR

  - id: no-eval-user-input
    patterns:
      - pattern: eval($X)
      - pattern-not: eval("...")
    message: "eval() with dynamic input — potential code injection"
    languages: [javascript, typescript]
    severity: ERROR

  - id: sql-injection-risk
    patterns:
      - pattern: |
          $QUERY = "..." + $INPUT + "..."
      - metavariable-regex:
          metavariable: $QUERY
          regex: ".*(SELECT|INSERT|UPDATE|DELETE).*"
    message: "SQL string concatenation — use parameterized queries"
    languages: [javascript, python]
    severity: ERROR

Gitleaks — Secret Scanning

# .gitleaks.toml
title = "Gitleaks Configuration"

[allowlist]
  paths = [
    '''\.test\.''',
    '''_test\.go''',
    '''testdata/''',
  ]

[[rules]]
  id = "aws-access-key"
  description = "AWS Access Key"
  regex = '''AKIA[0-9A-Z]{16}'''
  tags = ["aws", "credentials"]

[[rules]]
  id = "generic-api-key"
  description = "Generic API Key"
  regex = '''(?i)(api[_-]?key|apikey)\s*[:=]\s*['"][a-zA-Z0-9]{32,}['"]'''
  tags = ["api", "credentials"]

[[rules]]
  id = "private-key"
  description = "Private Key"
  regex = '''-----BEGIN (RSA |EC |DSA )?PRIVATE KEY-----'''
  tags = ["private-key"]

Checkov — Infrastructure Scanning

# Scan Terraform files
checkov -d terraform/ --framework terraform

# Scan with custom policy
checkov -d terraform/ \
  --external-checks-dir custom_policies/ \
  --check CKV_AWS_18,CKV_AWS_19,CKV_AWS_145

# Scan Kubernetes manifests
checkov -d k8s/ --framework kubernetes

# Scan Dockerfiles
checkov -f Dockerfile --framework dockerfile

Gating Strategy

This is where most teams go wrong. Here’s my approach:

Hard Gates (Block the PR)

  • Secrets detected — always block, no exceptions
  • Critical CVEs with known exploits — EPSS > 0.5
  • SQL injection / command injection — high-confidence SAST findings
  • Public S3 buckets in Terraform — infrastructure misconfigs

Soft Gates (Warn, Require Review)

  • High-severity CVEs without exploits — might be a false positive
  • Medium SAST findings — context-dependent
  • Deprecated dependency versions — track but don’t block

Skip (Don’t Even Report)

  • Info-level findings — noise that erodes trust
  • Findings in test files — different risk profile
  • Known false positives — maintain a suppression list
# .security/gate-config.yml
gates:
  hard_block:
    - secrets: all
    - sca:
        severity: [CRITICAL]
        epss_threshold: 0.5
    - sast:
        rules: [sql-injection, command-injection, ssrf]
        confidence: high
    - iac:
        checks: [CKV_AWS_18, CKV_AWS_19, CKV_AWS_20]

  soft_warn:
    - sca:
        severity: [HIGH]
    - sast:
        severity: [WARNING]

  suppress:
    - paths: ['**/test/**', '**/testdata/**']
    - ids: ['CVE-2023-XXXX']  # Known FP

Reducing False Positives

False positives are the #1 reason security pipelines fail. Developers stop trusting the tools and start ignoring findings.

Yes

No

Yes

No

Yes

No

Finding Reported

Is it real?

Is it exploitable?

Add to Suppress List

In production path?

Reduce Severity

Hard Block

Soft Warn

Reduce noise over time

Practical tips:

  1. Start in audit mode — report but don’t block for the first 2 weeks
  2. Tune aggressively — suppress false positives immediately with documented reasons
  3. Use SARIF — standardized format so all tools report to the same dashboard
  4. Track signal-to-noise ratio — if more than 20% of findings are false positives, tune more

Developer Experience

A security pipeline that annoys developers will be circumvented. Design for DX:

  • Fast — secret scanning and SAST should complete in under 2 minutes
  • Actionable — findings should include fix suggestions, not just vulnerability IDs
  • Inline — show findings as PR comments, not in a separate dashboard
  • Suppressible — let developers mark false positives with a comment and a reason
# Pre-commit hook for instant feedback
# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.18.0
    hooks:
      - id: gitleaks

  - repo: https://github.com/returntocorp/semgrep
    rev: v1.50.0
    hooks:
      - id: semgrep
        args: ['--config', 'p/security-audit', '--error']

Key Takeaways

  1. Five scan types, one pipeline — SAST, SCA, secrets, IaC, and DAST cover your bases
  2. Gate wisely — hard block on secrets and critical exploitable CVEs; warn on everything else
  3. Speed matters — if your security scan takes 10+ minutes, developers will hate it
  4. False positives kill pipelines — tune aggressively, maintain suppression lists
  5. Start in audit mode — collect data before blocking
  6. Automate the boring stuff — secret scanning and SCA should be fully automated

A well-tuned security pipeline catches real issues without slowing down development. It’s the foundation that makes all the other security practices in this course sustainable.

Share

Related Posts

Dependency Vulnerability Detection at Scale

Dependency Vulnerability Detection at Scale

The average application has over 200 transitive dependencies. Each one is code…

Supply Chain Security — Protecting Your Software Pipeline

Supply Chain Security — Protecting Your Software Pipeline

In 2024, a single malicious contributor nearly compromised every Linux system on…

Security Ticketing and Incident Response

Security Ticketing and Incident Response

The worst time to figure out your incident response process is during an…

Security Mindset for Engineers — Think Like an Attacker

Security Mindset for Engineers — Think Like an Attacker

Most engineers think about security the way they think about flossing — they…

Secrets Management — Vault, SSM, and Secrets Manager

Secrets Management — Vault, SSM, and Secrets Manager

I’ve watched a production database get wiped because someone committed a root…

OWASP Top 10 for Cloud Applications

OWASP Top 10 for Cloud Applications

The OWASP Top 10 was written for traditional web applications. But in the cloud…

Latest Posts

AI Video Generation in 2025 — Models, Costs, and How to Build a Cost-Effective Pipeline

AI Video Generation in 2025 — Models, Costs, and How to Build a Cost-Effective Pipeline

AI video generation went from “cool demo” to “usable in production” in 2024-202…

AI Models in 2025 — Cost, Capabilities, and Which One to Use

AI Models in 2025 — Cost, Capabilities, and Which One to Use

Choosing the right AI model is one of the most impactful decisions you’ll make…

AI Image Generation in 2025 — Models, Costs, and How to Optimize Spend

AI Image Generation in 2025 — Models, Costs, and How to Optimize Spend

Generating one image with AI costs between $0.002 and $0.12. That might sound…

AI Agents Demystified — It's Just Automation With a Better Brain

AI Agents Demystified — It's Just Automation With a Better Brain

Let’s cut through the noise. If you read Twitter or LinkedIn, you’d think “AI…

AI Coding Assistants in 2025 — Every Tool Compared, and Which One to Actually Use

AI Coding Assistants in 2025 — Every Tool Compared, and Which One to Actually Use

Two years ago, AI coding meant one thing: GitHub Copilot autocompleting your…

Supply Chain Security — Protecting Your Software Pipeline

Supply Chain Security — Protecting Your Software Pipeline

In 2024, a single malicious contributor nearly compromised every Linux system on…