DevSecOps Patterns
CI Security Pipeline
# GitHub Actions security pipeline
name: Security
on: [push, pull_request]
jobs:
sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Semgrep SAST
- uses: semgrep/semgrep-action@v1
with:
config: >-
p/owasp-top-ten
p/python
p/secrets
# Bandit (Python)
- run: pip install bandit && bandit -r src/ -ll -f json -o bandit.json
- uses: actions/upload-artifact@v4
with: { name: bandit-report, path: bandit.json }
dependency-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pip-audit -r requirements.txt --format json > pip-audit.json
- run: npm audit --json > npm-audit.json || true
- uses: snyk/actions/python@master
env: { SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} }
secret-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: gitleaks/gitleaks-action@v2
env: { GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} }
container-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: docker build -t myapp:${{ github.sha }} .
- uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
severity: HIGH,CRITICAL
exit-code: '1'
dast:
runs-on: ubuntu-latest
needs: [sast]
steps:
- uses: actions/checkout@v4
- name: Start app
run: docker-compose up -d
- uses: zaproxy/action-baseline@v0.10.0
with:
target: 'http://localhost:8000'
SAST Tools Comparison
# Semgrep — fast, rule-based, language-agnostic
semgrep --config auto src/
semgrep --config p/owasp-top-ten .
semgrep --config p/secrets .
# Bandit — Python focused
bandit -r . -ll # medium+ severity
bandit -r . -t B101,B102,B105 # specific checks
# CodeQL — deep semantic analysis (GitHub Actions built-in)
# .github/codeql/codeql-config.yml
# queries: +security-extended,+security-and-quality
# ESLint security plugin (Node.js)
# eslint-plugin-security, eslint-plugin-no-secrets
Dependency Management
# Python
pip-audit # audit installed packages against PyPI advisory db
safety check # check against Safety DB
pip install pip-audit && pip-audit -r requirements.txt
# Node.js
npm audit fix # auto-fix where possible
npx better-npm-audit check -l high # fail on high severity only
# Go
govulncheck ./... # official Go vulnerability checker
# Rust
cargo audit # check against RustSec Advisory DB
# Renovate / Dependabot — automated PRs for dependency updates
# .github/dependabot.yml
# package-ecosystem: pip
# schedule: interval: weekly
Policy as Code (OPA/Rego)
# Deny privileged containers
package main
deny[msg] {
input.kind == "Pod"
container := input.spec.containers[_]
container.securityContext.privileged == true
msg := sprintf("Container %s must not run as privileged", [container.name])
}
# Require non-root user
deny[msg] {
input.kind == "Deployment"
container := input.spec.template.spec.containers[_]
not container.securityContext.runAsNonRoot
msg := sprintf("Container %s must set runAsNonRoot=true", [container.name])
}
# Conftest — test K8s manifests against OPA policies
conftest test deployment.yaml --policy policy/
# Kyverno — admission controller with policy enforcement
kubectl apply -f kyverno-policy.yaml
Security Gates
# Quality gate — block merge on security findings
SECURITY_THRESHOLDS = {
"critical": 0, # zero tolerance
"high": 0, # zero tolerance
"medium": 5, # allow up to 5
"low": None # no limit
}
def check_security_gate(findings: list[dict]) -> bool:
counts = Counter(f["severity"].lower() for f in findings)
for severity, limit in SECURITY_THRESHOLDS.items():
if limit is not None and counts.get(severity, 0) > limit:
print(f"FAIL: {counts[severity]} {severity} findings (limit: {limit})")
return False
return True
Pre-commit Hooks
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
- repo: https://github.com/PyCQA/bandit
rev: 1.7.5
hooks:
- id: bandit
args: ["-ll", "--skip", "B101"]
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets
args: ['--baseline', '.secrets.baseline']