Security Can’t Be a Gate — It Must Be a Pipeline

Traditional security operates as a checkpoint: developers write code, security reviews it weeks later, and sends back a list of findings. By then, the code is in production, the developer has moved on, and the findings get deprioritized.

DevSecOps flips this model. Security is automated, continuous, and embedded directly into the development workflow. Every commit triggers security checks. Every pull request includes vulnerability data. Every deployment is validated against policy. Security becomes code.

The DevSecOps Pipeline

┌─────────┐  ┌──────────┐  ┌───────────┐  ┌──────────┐  ┌──────────┐
│  Code   │→ │  Build   │→ │   Test    │→ │  Deploy  │→ │ Monitor  │
└────┬────┘  └────┬─────┘  └─────┬─────┘  └────┬─────┘  └────┬─────┘
     │            │              │              │              │
  Pre-commit   SAST          DAST/IAST      Policy-as-   Runtime
  hooks        SCA           Integration    Code         Security
  Secrets      Container     Pen testing    RBAC         SIEM
  scanning     scanning      Fuzzing        Secrets      Alerting
               SBOM                         management

Each stage has specific tools and practices. Let’s build it.

Stage 1: Code — Pre-Commit Security

Secret Detection

The #1 preventable security incident: secrets committed to version control.

# Install gitleaks for pre-commit secret scanning
brew install gitleaks  # or go install github.com/gitleaks/gitleaks/v8@latest

# Scan entire repo history
gitleaks detect --source . --verbose

# Install as pre-commit hook
cat > .pre-commit-config.yaml << 'EOF'
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.18.0
    hooks:
      - id: gitleaks
EOF

pip install pre-commit
pre-commit install

Commit-Time Linting

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

  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
      - id: detect-private-key
      - id: check-added-large-files
        args: ['--maxkb=500']

  - repo: https://github.com/hadolint/hadolint
    rev: v2.12.0
    hooks:
      - id: hadolint-docker
        name: Lint Dockerfiles

  - repo: https://github.com/antonbabenko/pre-commit-terraform
    rev: v1.83.5
    hooks:
      - id: terraform_tfsec
        name: Terraform security scan

Stage 2: Build — Static Analysis and SCA

SAST (Static Application Security Testing)

Analyze source code for vulnerabilities without executing it:

# Semgrep — lightweight, fast, multi-language SAST
pip install semgrep

# Run with OWASP rules
semgrep scan --config "p/owasp-top-ten" --json -o sast-results.json

# Run with auto detection of language and framework
semgrep scan --config auto .

# Custom rules for your codebase
cat > .semgrep/custom-rules.yml << 'EOF'
rules:
  - id: no-eval-user-input
    pattern: eval($USER_INPUT)
    message: "Never eval user-controlled input"
    severity: ERROR
    languages: [python]

  - id: sql-string-concat
    patterns:
      - pattern: |
          $QUERY = "..." + $USER_INPUT + "..."
      - metavariable-pattern:
          metavariable: $QUERY
          pattern-regex: "(?i)(select|insert|update|delete)"
    message: "SQL query built via string concatenation"
    severity: ERROR
    languages: [python, javascript, java]
EOF

semgrep scan --config .semgrep/custom-rules.yml

SCA (Software Composition Analysis)

Your dependencies are your attack surface. 90%+ of modern applications are third-party code:

# Trivy — comprehensive vulnerability scanner
# Scans: container images, filesystems, git repos, IaC

# Scan project dependencies
trivy fs --scanners vuln,secret,misconfig .

# Scan with severity filter
trivy fs --severity CRITICAL,HIGH .

# Generate SBOM (Software Bill of Materials)
trivy fs --format spdx-json -o sbom.json .

# Scan container image
trivy image myapp:latest

# Scan Kubernetes manifests
trivy config ./k8s/

Container Image Scanning

# Scan during Docker build
# Dockerfile
FROM node:20-alpine AS build
COPY . .
RUN npm ci --production

# Scan the built image
trivy image --exit-code 1 --severity CRITICAL myapp:latest

# Alternatively, use Grype
grype myapp:latest --fail-on critical

SBOM Generation

Software Bill of Materials — know exactly what’s in your software:

# Generate SBOM with syft
syft myapp:latest -o spdx-json > sbom.spdx.json

# Scan SBOM for vulnerabilities
grype sbom:sbom.spdx.json

# Track SBOM in your CI artifacts
# Store alongside each release for supply chain transparency

Stage 3: Test — Dynamic Analysis

DAST (Dynamic Application Security Testing)

Test the running application from an attacker’s perspective:

# ZAP (Zed Attack Proxy) — automated DAST
docker run -t ghcr.io/zaproxy/zaproxy:stable zap-baseline.py \
    -t https://staging.myapp.com \
    -r zap-report.html \
    -l WARN

# Full scan with authentication
docker run -t ghcr.io/zaproxy/zaproxy:stable zap-full-scan.py \
    -t https://staging.myapp.com \
    -r zap-full-report.html \
    --hook=/zap/auth-hook.py

# Nuclei — fast vulnerability scanner with community templates
nuclei -u https://staging.myapp.com -t cves/ -t vulnerabilities/ -severity critical,high

API Security Testing

# Scan OpenAPI spec for security issues
spectral lint openapi.yaml

# Fuzz API endpoints
cat > api-fuzz.yaml << 'EOF'
fuzzing:
  - target: "{{BaseURL}}/api/v1/users"
    method: POST
    body: |
      {"email": "{{fuzz}}", "password": "{{fuzz}}"}
    payloads:
      fuzz:
        - type: file
          path: /usr/share/seclists/Fuzzing/special-chars.txt
EOF

nuclei -t api-fuzz.yaml -var BaseURL=https://staging.myapp.com

Stage 4: Deploy — Policy as Code

OPA (Open Policy Agent)

Define deployment policies programmatically:

# policy/kubernetes.rego
package kubernetes.admission

# Deny containers running as root
deny[msg] {
    input.request.kind.kind == "Pod"
    container := input.request.object.spec.containers[_]
    not container.securityContext.runAsNonRoot
    msg := sprintf("Container '%v' must set runAsNonRoot: true", [container.name])
}

# Deny images without digest
deny[msg] {
    input.request.kind.kind == "Pod"
    container := input.request.object.spec.containers[_]
    not contains(container.image, "@sha256:")
    msg := sprintf("Container '%v' must use image digest, not tag", [container.name])
}

# Deny privileged containers
deny[msg] {
    input.request.kind.kind == "Pod"
    container := input.request.object.spec.containers[_]
    container.securityContext.privileged
    msg := sprintf("Container '%v' must not be privileged", [container.name])
}

Secrets Management

# Never store secrets in code, environment variables, or config files

# Option 1: HashiCorp Vault
vault kv put secret/myapp/db password=supersecret
vault kv get -field=password secret/myapp/db

# Option 2: SOPS (encrypted files in git)
sops --encrypt --age $(cat ~/.config/sops/age/keys.txt | grep public | awk '{print $NF}') \
    secrets.yaml > secrets.enc.yaml

# Option 3: External Secrets Operator (Kubernetes)
cat > external-secret.yaml << 'EOF'
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: myapp-secrets
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: myapp-secrets
  data:
    - secretKey: db-password
      remoteRef:
        key: secret/myapp/db
        property: password
EOF

Stage 5: Monitor — Runtime Security

Falco — Runtime Threat Detection

# Detect suspicious behavior in production
# falco_rules.yaml
- rule: Shell Spawned in Container
  desc: Detect shell execution in a running container
  condition: >
    spawned_process and container and
    proc.name in (bash, sh, zsh, dash, ksh)
  output: >
    Shell spawned in container
    (user=%user.name container=%container.name
     shell=%proc.name parent=%proc.pname)
  priority: WARNING

- rule: Sensitive File Access
  desc: Detect read of sensitive files
  condition: >
    open_read and container and
    fd.name in (/etc/shadow, /etc/passwd, /proc/self/environ)
  output: >
    Sensitive file accessed
    (file=%fd.name user=%user.name container=%container.name)
  priority: CRITICAL

Security Alerting Pipeline

# GitHub Actions — complete security pipeline
name: Security Pipeline
on: [push, pull_request]

jobs:
  secrets:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: gitleaks/gitleaks-action@v2

  sast:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: returntocorp/semgrep-action@v1
        with:
          config: "p/owasp-top-ten"

  sca:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Trivy vulnerability scan
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          severity: 'CRITICAL,HIGH'
          exit-code: '1'

  container:
    runs-on: ubuntu-latest
    needs: [secrets, sast, sca]
    steps:
      - uses: actions/checkout@v4
      - name: Build image
        run: docker build -t myapp:${{ github.sha }} .
      - name: Scan image
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: 'myapp:${{ github.sha }}'
          exit-code: '1'
          severity: 'CRITICAL'
      - name: Generate SBOM
        run: |
          syft myapp:${{ github.sha }} -o spdx-json > sbom.json
      - uses: actions/upload-artifact@v4
        with:
          name: sbom
          path: sbom.json

Metrics That Matter

Track these to measure your DevSecOps maturity:

Mean Time to Remediate (MTTR):
  Critical: < 24 hours
  High:     < 7 days
  Medium:   < 30 days

Vulnerability Escape Rate:
  % of vulnerabilities that reach production
  Target: < 5%

Security Debt:
  Total unresolved findings weighted by severity
  Trend: decreasing quarter over quarter

Pipeline Block Rate:
  % of builds blocked by security gates
  Target: < 10% (too high = too noisy, developers will bypass)

Coverage:
  % of repos with security scanning enabled
  Target: 100%

Cultural Shift

Tools are the easy part. The hard part is culture:

  1. Security is everyone’s job — not a team you throw tickets at
  2. Automate everything — manual reviews don’t scale
  3. Shift left, but don’t shift blame — give developers the tools and training
  4. Reduce friction — if security slows delivery, people will bypass it
  5. Celebrate findings — a caught vulnerability is a win, not a failure

The pipeline is your security perimeter. Build it strong.