DevSecOps Basics Cheat Sheet
Shift-left security practices, pipeline scanning stages, and tooling for embedding security checks into CI/CD workflows.
Security Gates in a CI Pipeline
Shift-left scanning stages wired into a GitHub Actions workflow.
jobs: security: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 # SAST - scan source for vulnerable patterns - name: Semgrep SAST run: semgrep ci --config auto # SCA - scan dependencies for known CVEs - name: Dependency scan run: npm audit --audit-level=high # Secret scanning - name: Gitleaks run: gitleaks detect --source . --exit-code 1 # Container image scan (after build) - name: Trivy image scan run: trivy image --exit-code 1 --severity HIGH,CRITICAL my-app:${{ github.sha }}
Infrastructure-as-Code Scanning
Catch misconfigured cloud resources before they're ever applied.
# Scan Terraform for misconfigurations before plan/applycheckov -d ./terraform --framework terraform --compact# Alternative: tfsec, focused purely on Terraformtfsec ./terraform
Shift-Left Stages
Where each class of security check belongs in the SDLC.
- IDE / pre-commit- linting, secret detection, dependency version checks
- SAST- static analysis of source code for vulnerable patterns
- SCA- scan third-party dependencies for known CVEs and license issues
- IaC scanning- Terraform/CloudFormation misconfig checks before apply
- Container scanning- image CVE scan post-build, pre-push to registry
- DAST- dynamic scan against a running staging environment
- Runtime/CSPM- continuous posture monitoring in production
DAST Baseline Scan
Dynamic scan against a running staging deployment, gated on high-confidence findings only.
# DAST - dynamic scan against a running staging environmentzap-baseline.py \ -t https://staging.example.com \ -r zap-report.html \ -J zap-report.json \ -I # don't fail build on warnings, only on explicit gate below# Fail the build only on high-risk, high-confidence alertsjq -e '.site[].alerts[] | select(.riskcode=="3" and .confidence=="3")' zap-report.json && exit 1exit 0
SBOM Generation & Image Signing
Generate a signed SBOM attestation for the build artifact using Syft and Sigstore cosign.
# Generate a Software Bill of Materials for the built imagesyft my-app:${GITHUB_SHA} -o cyclonedx-json > sbom.json# Keyless-sign the image and attach the SBOM as an attestationcosign sign --yes my-registry/my-app:${GITHUB_SHA}cosign attest --yes --predicate sbom.json --type cyclonedx my-registry/my-app:${GITHUB_SHA}# Verify provenance before deploy - reject unsigned imagescosign verify \ --certificate-identity-regexp '.*' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ my-registry/my-app:${GITHUB_SHA}
Policy-as-Code Gate
Block insecure Kubernetes manifests from merging using an OPA/Conftest rego policy.
# policy/deployment.regopackage maindeny[msg] { input.kind == "Deployment" c := input.spec.template.spec.containers[_] not c.securityContext.runAsNonRoot msg := sprintf("container %v must set runAsNonRoot", [c.name])}deny[msg] { input.kind == "Deployment" c := input.spec.template.spec.containers[_] not c.resources.limits msg := sprintf("container %v is missing resource limits", [c.name])}# Gate the pipeline on the policyconftest test k8s/deployment.yaml --policy policy/
DevSecOps Maturity Metrics
KPIs that show whether a security program is actually working, not just running.
- MTTR (security)- mean time to remediate a critical/high finding, from detection to merged fix
- Scan coverage- % of repos/services with SAST, SCA, and secret scanning enabled and passing
- MTTD- mean time to detect: gap between vulnerability introduction and first scan catching it
- False positive rate- noisy scanners get disabled; track and tune suppression rules
- Escaped defects- vulnerabilities found in production that should've been caught pre-merge
- Policy pass rate- % of builds passing security gates without a manual override
- Dependency freshness- median age of outdated/vulnerable dependencies across services
Dynamic Secrets in CI
Fetch short-lived, auto-expiring credentials instead of storing static secrets in the pipeline.
# Authenticate the CI job to Vault via its OIDC/JWT identityvault login -method=jwt role=ci-pipeline jwt="$CI_JOB_JWT"# Request a leased, auto-revoking database credentialDB_CREDS=$(vault read -format=json database/creds/readonly-role)export DB_USER=$(echo "$DB_CREDS" | jq -r '.data.username')export DB_PASS=$(echo "$DB_CREDS" | jq -r '.data.password')# Credential auto-revokes after its lease (e.g. 1h) - nothing to rotate manually
Start every DevSecOps rollout by making scanners advisory (warn, don't fail the build) for the first few weeks — flipping straight to hard-fail floods teams with pre-existing findings and gets the pipeline disabled instead of fixed.