DevSecOps Guide 2026 — Security in Every Stage of the CI/CD Pipeline

Sanjeev SharmaSanjeev Sharma
6 min read

Advertisement

Introduction

Why This Matters

The average data breach costs $4.45 million and takes 277 days to identify. DevSecOps catches vulnerabilities at the source — during development and in CI/CD — when fixes cost minutes instead of millions. Shifting security left means automated scanning on every commit, not a manual penetration test after the product ships. This guide builds a complete security scanning pipeline you can drop into any GitHub Actions workflow.

SAST: Static Code Analysis with CodeQL

# .github/workflows/security.yml
name: Security Scanning
 
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 8 * * 1'  # Weekly on Monday
 
jobs:
  codeql:
    name: CodeQL Analysis
    runs-on: ubuntu-latest
    permissions:
      security-events: write
      actions: read
      contents: read
 
    strategy:
      fail-fast: false
      matrix:
        language: ['javascript', 'typescript']
 
    steps:
      - uses: actions/checkout@v4
 
      - uses: github/codeql-action/init@v3
        with:
          languages: ${{ matrix.language }}
          queries: security-and-quality
 
      - uses: github/codeql-action/autobuild@v3
 
      - uses: github/codeql-action/analyze@v3
        with:
          category: '/language:${{ matrix.language }}'

Dependency Scanning

  dependency-scan:
    name: Dependency Vulnerabilities
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - name: npm audit
        run: npm audit --audit-level=high
        # Fails if HIGH or CRITICAL vulnerabilities found
 
      - name: Snyk scan
        uses: snyk/actions/node@master
        with:
          args: --severity-threshold=high
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
 
      - name: OWASP Dependency Check
        uses: dependency-check/Dependency-Check_Action@main
        with:
          project: 'my-app'
          path: '.'
          format: 'HTML'
          args: >
            --failOnCVSS 7
            --enableRetired
 
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: dependency-report
          path: reports/

Secrets Detection

  secrets-scan:
    name: Secrets Detection
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0   # Full history for Gitleaks
 
      - name: Gitleaks
        uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 
      - name: TruffleHog
        uses: trufflesecurity/trufflehog@main
        with:
          path: ./
          base: ${{ github.event.repository.default_branch }}
          extra_args: --only-verified
# .gitleaks.toml — custom rules
[[rules]]
description = "Custom API Key"
regex = '''(?i)myapp[_-]?api[_-]?key['":=\s]+[a-zA-Z0-9]{20,}'''
tags = ["api", "custom"]
 
[allowlist]
paths    = [".gitleaks.toml", "tests/fixtures/"]
regexes  = ["EXAMPLE_KEY", "YOUR_KEY_HERE", "test_key_.*"]

Container Security Scanning

  container-scan:
    name: Container Vulnerability Scan
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Trivy scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: 'ghcr.io/${{ github.repository }}:${{ github.sha }}'
          format: 'sarif'
          output: 'trivy-results.sarif'
          severity: 'CRITICAL,HIGH'
          exit-code: '1'
 
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: 'trivy-results.sarif'

Harden your Dockerfile to reduce attack surface:

# Use minimal Alpine base
FROM node:20-alpine
 
# Install security updates first
RUN apk update && apk upgrade && rm -rf /var/cache/apk/*
 
# Create non-root user
RUN addgroup --system --gid 1001 nodejs && \
    adduser  --system --uid 1001 nodeuser
 
WORKDIR /app
 
# Copy and install dependencies
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
 
# Copy application with correct ownership
COPY --chown=nodeuser:nodejs . .
 
# Switch to non-root user
USER nodeuser
 
EXPOSE 3000
 
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1
 
CMD ["node", "dist/server.js"]

SBOM: Software Bill of Materials

  sbom:
    name: Generate SBOM
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - name: Generate SBOM with Syft
        uses: anchore/sbom-action@v0
        with:
          format: spdx-json
          output-file: sbom.spdx.json
 
      - name: Scan SBOM with Grype
        uses: anchore/scan-action@v3
        with:
          sbom: sbom.spdx.json
          fail-build: true
          severity-cutoff: high
 
      - uses: actions/upload-artifact@v4
        with:
          name: sbom
          path: sbom.spdx.json

Infrastructure Security with Terraform

# Least-privilege security group — allow only HTTPS inbound
resource "aws_security_group" "api" {
  name   = "api-sg"
  vpc_id = aws_vpc.main.id
 
  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
 
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    description = "HTTP — redirect to HTTPS only"
  }
 
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

Run checkov to scan Terraform for misconfigurations:

pip install checkov
checkov -d terraform/ --framework terraform --quiet
 
# Or in CI
checkov -d terraform/ \
  --soft-fail \
  --output-file-path checkov-results.xml \
  --output sarif

PR Security Checklist

## Security Review Checklist
 
- [ ] Gitleaks passed — no secrets in diff
- [ ] npm audit passed — no HIGH or CRITICAL CVEs
- [ ] Input validated on all new API endpoints
- [ ] Authorization check for user-owned resources
- [ ] SQL queries use parameterized statements only
- [ ] New third-party packages reviewed for security
- [ ] Sensitive fields not included in logs
- [ ] New environment variables documented and added to Vault/Secrets Manager

Common Mistakes

  • Ignoring npm audit warningsmoderate severity vulnerabilities become critical when chained with other issues
  • Scanning only on merge to main — by then, the vulnerable code is in the default branch; scan on every PR
  • No SBOM generation — without a software bill of materials, you cannot quickly assess exposure to new CVEs like Log4Shell
  • Running containers as root — most container images default to root; always set USER nonroot in your Dockerfile
  • SSH port 22 open to 0.0.0.0/0 — use AWS SSM Session Manager or restrict SSH to specific bastion IPs

Best Practices

  • Make security gates non-blocking in the first week when adopting DevSecOps — build trust before enforcing failures
  • Store all secrets in a secret manager (Vault, AWS Secrets Manager) and rotate them automatically; never commit .env files
  • Enable Dependabot in every repository to get automatic PRs for dependency security patches
  • Use separate AWS accounts for production and development — limits blast radius of compromised credentials
  • Review CodeQL Security Advisories weekly — GitHub notifies you when new CVEs affect your dependencies

Key Takeaways

  • CodeQL performs deep static analysis that finds injection flaws, XSS, and authentication bypasses that linters miss
  • Gitleaks scans the full git history, not just the current diff — it catches secrets committed months ago
  • Trivy scans OS packages, language dependencies, and Kubernetes manifests in a single tool with near-zero false positives
  • SBOM (Software Bill of Materials) in SPDX or CycloneDX format documents every dependency so you can assess CVE exposure instantly
  • Running containers as non-root eliminates an entire class of privilege escalation exploits at zero performance cost
  • npm audit --audit-level=high fails the build on HIGH and CRITICAL CVEs — the right threshold for most production applications
  • DevSecOps does not slow teams down — automated scanning is faster than manual security reviews and catches more issues
  • IaC scanning with checkov or tfsec finds misconfigurations (open security groups, unencrypted S3) before terraform apply

Advertisement

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro

Related reading