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}}"
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
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 }}
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 }}
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'
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:
- Clone a public Node.js app (e.g.,
express-generatorscaffold) - Create
.github/workflows/devsecops.ymlwith CodeQL, Trivy, and Gitleaks jobs - Set Trivy to fail the build only on CRITICAL vulnerabilities
- Create a
.gitleaks.tomlfile that allowslist yourtest/anddocs/directories - Commit a file containing
aws_secret_access_key = AKIAIOSFODNN7EXAMPLEto test Gitleaks catches it - Push to a feature branch, open a PR, and verify all three gates run
- 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.
Comments (0)
This is exactly what I needed! The initContainer approach solved our migration issues completely. Thanks for the detailed guide!
ReplyLeave a Comment