Security is not a phase — it's a pipeline stage. Modern DevOps teams embed security checks into CI/CD workflows, catching vulnerabilities before they reach production. In this lesson, you'll integrate SAST, DAST, dependency scanning, secret detection, and SBOM generation into your GitHub Actions pipelines. By the end, your pipelines will actively defend your supply chain.

1. Learning Objectives

By the end of this lesson, you will be able to:

  • Define SAST, DAST, dependency scanning, and secret detection and explain when each applies
  • Integrate CodeQL, Trivy, and Gitleaks into GitHub Actions workflows
  • Implement fail-fast and warning-only gates for security scan results
  • Generate and publish SPDX SBOM documents from your build pipeline
  • Configure dependency review alerts on pull requests
  • Build a DevSecOps pipeline that enforces security before deployment

2. Why This Matters

Real-world scenario: You deploy a Node.js app to production on Friday. Monday morning, Snyk reports lodash@4.17.20 has a critical prototype pollution vulnerability (CVE-2024-23346). Your application has been exposed all weekend. The fix? A security gate in your pipeline that would have blocked the PR until the dependency was updated. Breaches exploiting known CVEs account for over 60% of data breaches. Embedding security scans into CI/CD — DevSecOps — catches these before they ship.

3. Core Concepts

SAST — Static Application Security Testing

SAST analyzes source code without executing it. It scans for insecure patterns — SQL injection, XSS, hardcoded secrets, buffer overflows — during the build phase. Tools: GitHub CodeQL, SonarQube, Semgrep, Checkmarx. SAST runs early (shift-left) and produces low false-positive rates when well-configured.

DAST — Dynamic Application Security Testing

DAST tests a running application from the outside. It sends crafted payloads to endpoints and observes responses — looking for CSRF, authentication bypass, injection, and misconfiguration. Tools: OWASP ZAP, Burp Suite, Acunetix. DAST runs against staging environments after deployment, catching runtime issues SAST misses.

Dependency Scanning

Dependency scanning checks open-source libraries against CVE databases (NVD, GitHub Advisory, OSV). It flags known vulnerabilities in your package.json, requirements.txt, go.mod, Gemfile, etc. Tools: Dependabot, Snyk, Trivy, Renovate. Always set a severity threshold — fail builds on CRITICAL/HIGH, warn on MEDIUM/LOW.

Secret Detection

Secret detection scans for API keys, tokens, passwords, and private keys committed to the repository. Tools: Gitleaks, TruffleHog, GitHub secret scanning. Even a single AWS key in a public repo can cost thousands in minutes. Run secret detection on every push.

SBOM — Software Bill of Materials

An SBOM is a machine-readable inventory of all components in your application — direct and transitive dependencies, licenses, versions. The SPDX and CycloneDX formats are industry standards. SBOMs are required by US Executive Order 14028 for federal software suppliers. Generate one in each build and archive it as a release artifact.

4. Hands-On Practice: Building a DevSecOps Pipeline

Step 1: SAST with CodeQL

CodeQL is GitHub's native SAST engine. Add this to your repository under .github/workflows/codeql.yml:

name: CodeQL SAST

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 6 * * 1'  # Every Monday at 6am

jobs:
  analyze:
    name: Analyze (${{ matrix.language }})
    runs-on: ubuntu-latest
    permissions:
      security-events: write
      actions: read
      contents: read

    strategy:
      fail-fast: false
      matrix:
        language: ['javascript', 'python', 'ruby']

    steps:
      - uses: actions/checkout@v4

      - name: Initialize CodeQL
        uses: github/codeql-action/init@v3
        with:
          languages: ${{ matrix.language }}
          queries: security-and-quality

      - name: Autobuild
        uses: github/codeql-action/autobuild@v3

      - name: Perform Analysis
        uses: github/codeql-action/analyze@v3
        with:
          output: sarif-results
          category: "/language:${{matrix.language}}"
CodeQL SAST workflow — runs on push, PR, and weekly schedule

Step 2: Dependency Scanning with Trivy

Trivy (by Aqua Security) scans container images, filesystems, and Git repos for CVEs. Add this as a job in your CI:

name: Trivy Security Scan

on:
  pull_request:
    branches: [main]

jobs:
  trivy-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: fs
          scan-ref: .
          format: sarif
          output: trivy-results.sarif
          severity: CRITICAL,HIGH

      - name: Upload Trivy results
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: trivy-results.sarif
          category: trivy

      - name: Fail on critical findings
        run: |
          if [ -f trivy-results.sarif ]; then
            COUNT=$(jq '.runs[0].results | length' trivy-results.sarif)
            echo "CRITICAL/HIGH findings: $COUNT"
            if [ "$COUNT" -gt 0 ]; then
              echo "::error::$COUNT critical/high vulnerabilities found"
              exit 1
            fi
          fi
Trivy filesystem scan — fails on CRITICAL or HIGH vulnerabilities

Step 3: Secret Detection with Gitleaks

Gitleaks scans commits for secrets. Run it on every PR to catch tokens before they hit the main branch:

name: Gitleaks Secret Scan

on:
  pull_request:
    branches: [main]

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

      - name: Scan for secrets
        uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Gitleaks action — scans full commit history for secrets

Step 4: SBOM Generation with Anchore Syft

Generate an SPDX SBOM for every build and archive it:

name: SBOM Generation

on:
  push:
    branches: [main]

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

      - name: Generate SBOM
        uses: anchore/sbom-action@v0
        with:
          format: spdx-json
          output-file: sbom.spdx.json

      - name: Upload SBOM as artifact
        uses: actions/upload-artifact@v4
        with:
          name: sbom
          path: sbom.spdx.json

      - name: Publish SBOM to release
        if: startsWith(github.ref, 'refs/tags/')
        run: |
          gh release upload ${{ github.ref_name }} sbom.spdx.json
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SBOM generation with Anchore Syft — archived on every build, attached to releases

Step 5: Full DevSecOps Pipeline — All Together

Combine all security stages into a single pipeline with gates at each step:

name: DevSecOps Pipeline

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

jobs:
  # Gate 1 — SAST
  codeql:
    uses: ./.github/workflows/codeql.yml
    secrets: inherit

  # Gate 2 — Secret Detection
  secrets:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  # Gate 3 — Dependency Scan
  dependencies:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: aquasecurity/trivy-action@master
        with:
          scan-type: fs
          scan-ref: .
          format: table
          exit-code: 1
          severity: CRITICAL,HIGH

  # Gate 4 — Build and Unit Tests
  build:
    needs: [codeql, secrets, dependencies]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm test
      - run: npm run build

  # Gate 5 — DAST (on staging deployment)
  dast:
    needs: [build]
    runs-on: ubuntu-latest
    steps:
      - name: Start ZAP DAST scan
        uses: zaproxy/action-full-scan@v0
        with:
          target: 'https://staging.example.com'
          rules_file_name: .zap/rules.tsv
          cmd_options: '-a'
Complete DevSecOps pipeline — 5 gates before deployment is allowed

5. Common Errors and Solutions

1. Trivy exits with code 1 on every scan

Trivy exits non-zero by default when it finds any vulnerability. Use exit-code: 0 in the action config and handle the SARIF/table output yourself, or set severity: CRITICAL,HIGH to only fail on serious issues.

2. Gitleaks finds false positives — build fails

Create a .gitleaks.toml allowlist at the repo root:

# .gitleaks.toml
[allowlist]
description = "Known safe values that look like secrets"
paths = [
  '''(test|spec|__tests__)/.*''',
  '''.*\.md$''',
]
regexes = [
  '''example\.com''',
  '''test-token-.*''',
]

3. CodeQL takes too long (30+ minutes)

Set queries: security-extended instead of security-and-quality — it runs a smaller query set. Also set ram: 4096 and threads: 4 in the init step for faster parallel execution.

4. Dependency review API returns 404

The dependency-review-action requires the repository to have Dependabot alerts enabled. Go to Settings → Code security → Dependabot alerts → Enable. Also ensure contents: read and pull-requests: write permissions are set on the job.

5. SBOM generation fails on monorepos

Syft detects package managers automatically but can be confused by nested package files. Use scope: all-layers with the container image scan, or pass --exclude './vendor/**' to narrow the scan scope.

6. Summary Checklist

  • SAST (CodeQL) integrated — runs on every push and PR
  • Dependency scanning (Trivy) configured with CRITICAL/HIGH fail gate
  • Secret detection (Gitleaks) running on every PR
  • SBOM (SPDX) generated and archived per release
  • DAST (ZAP) scheduled against staging environment
  • Dependency review enabled in GitHub repo settings
  • Reusable security workflows extracted to .github/workflows/
  • Allowlist rules added for known false positives

7. Practice Exercise

Set up a DevSecOps pipeline for a sample repository:

  1. Clone a public Node.js app (e.g., express-generator scaffold)
  2. Create .github/workflows/devsecops.yml with CodeQL, Trivy, and Gitleaks jobs
  3. Set Trivy to fail the build only on CRITICAL vulnerabilities
  4. Create a .gitleaks.toml file that allowslist your test/ and docs/ directories
  5. Commit a file containing aws_secret_access_key = AKIAIOSFODNN7EXAMPLE to test Gitleaks catches it
  6. Push to a feature branch, open a PR, and verify all three gates run
  7. Generate an SBOM with syft dir:. -o spdx-json=sbom.json

8. Next Steps

You've learned to embed security into every stage of your pipeline. The next frontier is supply chain security — signing artifacts with Sigstore/cosign, verifying attestations with in-toto, and enforcing SLSA compliance levels. In the next CI/CD lesson, you'll build a software supply chain that meets SLSA Level 3 requirements.

Gataya Med

DevOps Engineer & Backend Developer. Sharing insights on cloud, automation, and scalable systems.

Comments (0)

Sarah Chen July 27, 2026

This is exactly what I needed! The initContainer approach solved our migration issues completely. Thanks for the detailed guide!

Reply

Leave a Comment