| name | devsecops-patterns |
| description | DevSecOps patterns — shift-left security, SAST (semgrep/CodeQL), secrets detection (gitleaks/trufflehog), dependency scanning (trivy/grype), DAST, OPA/Falco policy-as-code, container security, and security gates per CI stage. |
Skill: DevSecOps & Security Automation
DevSecOps integrates security into every phase of the SDLC — shifting left from post-release penetration tests to pre-commit hooks and PR gates. Security is continuous, automated, developer-friendly, and not a final checkpoint.
When to Activate
- Setting up SAST (Semgrep, CodeQL) in CI pipelines
- Configuring secrets detection (Gitleaks) as pre-commit hooks
- Implementing dependency vulnerability scanning (Trivy, Snyk)
- Adding container security scanning to image build pipelines
- Setting up OWASP ZAP for DAST in test environments
- Implementing Policy-as-Code with OPA/Gatekeeper or Kyverno
- Establishing a Security Champions program
- Designing a Security Gate strategy (pre-commit → PR → pre-release)
Shift-Left Security
Security Feedback Loop Speeds
Pre-commit hook → < 5 seconds (Gitleaks, fast Semgrep rules)
PR Gate → < 5 minutes (Trivy, full Semgrep, ZAP Baseline)
Pre-Release Gate → < 30 minutes (Full DAST, license scan, SBOM)
Security Champions Program
One engineer per product team acts as the Security Champion:
- Attends monthly security guild meeting
- Reviews security findings in their team's applications
- Escalates critical findings to the security team
- Trains teammates on secure coding patterns
SAST — Static Analysis Security Testing
Semgrep (Primary SAST Tool)
semgrep --config=auto .
git diff --name-only HEAD | xargs semgrep --config=auto
Semgrep Custom Rule
rules:
- id: no-hardcoded-api-key
patterns:
- pattern: |
$KEY = "..."
- metavariable-regex:
metavariable: $KEY
regex: '(?i)(api_key|apikey|secret|password|token|auth)'
- metavariable-regex:
metavariable: $VALUE
regex: '[A-Za-z0-9+/]{20,}'
message: "Potential hardcoded secret in variable $KEY"
severity: ERROR
languages: [python, javascript, typescript, go, java]
metadata:
category: security
cwe: "CWE-798"
owasp: "A02:2021"
GitHub Actions — SAST Gate
jobs:
semgrep:
runs-on: ubuntu-latest
container:
image: semgrep/semgrep
steps:
- uses: actions/checkout@v4
- run: |
semgrep ci --config=auto --severity ERROR --error \
--sarif-output=semgrep-results.sarif
env:
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: semgrep-results.sarif
codeql:
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- uses: actions/checkout@v4
- uses: github/codeql-action/init@v3
with:
languages: javascript, python
- uses: github/codeql-action/autobuild@v3
- uses: github/codeql-action/analyze@v3
Language-Specific SAST Tools
| Language | Tool | Command |
|---|
| Python | Bandit | bandit -r src/ -ll |
| Go | gosec | gosec ./... |
| Ruby | Brakeman | brakeman -q -w2 . |
| JavaScript | ESLint security | eslint --plugin security src/ |
| Java | SpotBugs + FindSecBugs | mvn spotbugs:check |
Secrets Detection
Gitleaks — Pre-Commit + CI
gitleaks protect --staged
gitleaks detect --source=. --log-opts="--all" --verbose
Pre-Commit Hook Configuration
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
entry: gitleaks protect --staged -v
pass_filenames: false
Gitleaks Configuration
[extend]
useDefault = true
[[rules]]
description = "Internal API Key"
id = "internal-api-key"
regex = '''MYAPP_[A-Z0-9]{32}'''
[allowlist]
regexes = ["test-api-key-12345"]
paths = [".git", "tests/fixtures/", "*.test.ts"]
TruffleHog — Deep History Scan
trufflehog git file://. --since-commit HEAD~100 --only-verified
trufflehog github --org=myorg --token=$GITHUB_TOKEN
Secret Rotation Runbook
When a secret is discovered in Git:
- Immediately revoke at the provider (AWS IAM, GitHub PAT, Stripe)
- Generate a new secret and store in the secret manager
- Remove from Git history with BFG Repo Cleaner, then
git push --force-with-lease
- Notify affected parties (if the secret had production access)
- Audit the secret's usage in access logs for the past 30 days
Dependency Vulnerability Scanning
Trivy — All-in-One Scanner
trivy fs . --severity HIGH,CRITICAL
trivy image myorg/my-app:latest --severity CRITICAL --exit-code 1
trivy fs . --format spdx-json --output sbom.json
GitHub Actions — Trivy Container Scan
- uses: aquasecurity/trivy-action@master
with:
image-ref: myorg/my-app:${{ github.sha }}
format: sarif
output: trivy-results.sarif
severity: HIGH,CRITICAL
exit-code: '1'
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: trivy-results.sarif
Language-Native Scanners
npm audit --audit-level=high
pip-audit -r requirements.txt
govulncheck ./...
cargo audit
bundle exec bundle-audit check
DAST — Dynamic Analysis Security Testing
OWASP ZAP Baseline Scan (PR Gate — Safe)
The Baseline Scan only performs passive scanning — it does not modify data. Safe to run against any environment.
docker run --rm \
-v $(pwd):/zap/wrk \
ghcr.io/zaproxy/zaproxy:stable \
zap-baseline.py \
-t http://my-app:8080 \
-r zap-report.html \
-J zap-report.json \
-I
ZAP Baseline in GitHub Actions
- name: Start app
run: docker compose up -d
- name: Wait for app to be ready
run: timeout 60 bash -c 'until curl -sf http://localhost:8080/health; do sleep 2; done'
- name: OWASP ZAP Baseline Scan
uses: zaproxy/action-baseline@v0.12.0
with:
target: 'http://localhost:8080'
rules_file_name: '.zap/rules.tsv'
cmd_options: '-I'
fail_action: false
- uses: actions/upload-artifact@v4
with:
name: zap-report
path: report_html.html
ZAP Full Scan (Pre-Release — Test Environments Only)
docker run --rm \
-v $(pwd):/zap/wrk \
ghcr.io/zaproxy/zaproxy:stable \
zap-full-scan.py \
-t http://test-app:8080 \
-r full-scan-report.html \
-z "-config scanner.attackStrength=HIGH"
Policy-as-Code
OPA + Gatekeeper (Kubernetes Admission Controller)
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: requirelabels
spec:
crd:
spec:
names:
kind: RequireLabels
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package requirelabels
violation[{"msg": msg}] {
required := {label | label := input.parameters.labels[_]}
provided := {label | input.review.object.metadata.labels[label]}
missing := required - provided
count(missing) > 0
msg := sprintf("Missing required labels: %v", [missing])
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: RequireLabels
metadata:
name: require-pod-labels
spec:
match:
kinds: [{apiGroups: [""], kinds: ["Pod"]}]
namespaces: ["production", "staging"]
parameters:
labels: ["app", "team", "version"]
Conftest — Test Policies Locally
cat > policy/no-root.rego << 'EOF'
package main
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])
}
EOF
conftest test k8s/ --policy policy/
Falco — Runtime Threat Detection
- rule: Suspicious shell in container
desc: Detect shell spawned in a container
condition: >
spawned_process and container
and proc.name in (shell_binaries)
and not proc.pname in (known_shell_spawn_binaries)
output: >
Shell spawned in container (user=%user.name container=%container.name
image=%container.image.repository proc=%proc.name parent=%proc.pname)
priority: WARNING
tags: [container, shell, T1059]
- rule: Write to sensitive directory
desc: Detect writes to /etc or /usr in containers
condition: >
open_write and container
and (fd.directory startswith /etc or fd.directory startswith /usr/bin)
output: >
Sensitive file write (user=%user.name file=%fd.name
container=%container.name image=%container.image.repository)
priority: ERROR
tags: [container, filesystem, T1222]
Container Security
Secure Dockerfile Patterns
# Multi-stage build — don't ship build tools
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json .
RUN npm ci --only=production
# Use distroless for minimal attack surface
FROM gcr.io/distroless/nodejs22-debian12:nonroot AS runtime
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
# No shell, no package manager
USER nonroot:nonroot
# Read-only root filesystem set via securityContext in Kubernetes
Image Signing with Sigstore/cosign
cosign sign --key cosign.key myorg/my-app:${{ github.sha }}
cosign verify --key cosign.pub myorg/my-app:latest
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signatures
spec:
validationFailureAction: enforce
rules:
- name: verify-signature
match:
resources:
kinds: [Pod]
verifyImages:
- imageReferences: ["myorg/*"]
attestors:
- entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
<your-cosign-public-key>
-----END PUBLIC KEY-----
Security Gates by Stage
┌─────────────────────────────────────────────────────────────────────┐
│ IDE / Local │
│ • Semgrep IDE plugin (VSCode, IntelliJ) │
│ • gitleaks protect --staged (pre-commit hook) │
└──────────────────────────────┬──────────────────────────────────────┘
│ git push
┌──────────────────────────────▼──────────────────────────────────────┐
│ PR Gate (< 5 min, blocks merge on CRITICAL) │
│ • Semgrep --config=auto │
│ • Gitleaks detect │
│ • Trivy fs . --severity CRITICAL │
│ • ZAP Baseline Scan (passive, non-destructive) │
│ • Conftest / OPA against changed manifests │
└──────────────────────────────┬──────────────────────────────────────┘
│ merge to main
┌──────────────────────────────▼──────────────────────────────────────┐
│ Image Build Gate (< 10 min, blocks push on CRITICAL) │
│ • Trivy image scan │
│ • cosign sign │
│ • SBOM generation (trivy --format spdx-json) │
└──────────────────────────────┬──────────────────────────────────────┘
│ pre-release
┌──────────────────────────────▼──────────────────────────────────────┐
│ Pre-Release Gate (< 30 min, manual approval gate) │
│ • ZAP Full Scan (test environment) │
│ • Nuclei CVE scan │
│ • Dependency license compliance check │
│ • DefectDojo / Security Hub findings review │
└─────────────────────────────────────────────────────────────────────┘
Security Findings Dashboard
Centralize all findings in DefectDojo or AWS Security Hub:
curl -X POST https://defectdojo.myorg.com/api/v2/import-scan/ \
-H "Authorization: Token $DEFECTDOJO_TOKEN" \
-F "scan_type=SARIF" \
-F "file=@trivy-results.sarif" \
-F "engagement=1" \
-F "product=my-app" \
-F "minimum_severity=High"
DevSecOps Maturity
| Level | What's Automated |
|---|
| Level 0 | No security automation |
| Level 1 | Gitleaks pre-commit + npm audit in CI |
| Level 2 | Semgrep + Trivy in PR gate, blocks on CRITICAL |
| Level 3 | ZAP DAST + Policy-as-Code (OPA/Kyverno) + Falco runtime |
| Level 4 | Full pipeline with signing, SBOM, centralized findings dashboard |