The standard startup security posture: ship fast, do a security review before Series B, discover that base images have 40 critical CVEs, secrets are in .env files, and the database is publicly accessible. Then spend two weeks doing emergency remediation.
DevSecOps is the discipline of moving that security work left - into the development and CI process, not the fundraising process. Done right, it adds 5–10 minutes to a CI pipeline and catches 80% of the problems that show up in penetration tests.
The Four Gates
Security in CI/CD works as four gates. Each gate runs in the pipeline and blocks a deploy if it fails above a threshold.
Gate 1: Static Analysis (SAST)
SAST tools scan your source code for security vulnerabilities - SQL injection patterns, hardcoded credentials, insecure cryptography, missing input validation.
Semgrep is the best open-source option. Fast, extensible, and free:
yaml# .github/workflows/security.yaml name: Security on: [push, pull_request] jobs: sast: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run Semgrep SAST uses: semgrep/semgrep-action@v1 with: config: >- p/owasp-top-ten p/nodejs p/secrets auditOn: push env: SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
Semgrep runs rules against your source code and fails the pipeline if it finds critical issues. The p/secrets ruleset catches hardcoded API keys, passwords, and tokens in code. The p/owasp-top-ten ruleset catches injection, XSS, and other common vulnerabilities.
Gate 2: Dependency Scanning (SCA)
Software Composition Analysis scans your package dependencies for known CVEs. The average Node.js application has 700+ transitive dependencies - most developers have no idea what CVEs are in there.
yamldependency-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run Trivy dependency scan uses: aquasecurity/trivy-action@master with: scan-type: fs scan-ref: . format: table exit-code: 1 severity: CRITICAL,HIGH ignore-unfixed: true # skip CVEs with no fix available
Trivy scans package.json, requirements.txt, go.mod, Gemfile, etc. and fails the pipeline on HIGH or CRITICAL CVEs that have available fixes. The ignore-unfixed flag prevents noise from CVEs the maintainers have not patched yet.
Gate 3: Container Image Scanning
Your application code might be clean. Your base image might not be. Alpine 3.14 and Ubuntu 20.04 base images have hundreds of known CVEs by now.
yamlimage-scan: runs-on: ubuntu-latest needs: [build] steps: - name: Run Trivy image scan uses: aquasecurity/trivy-action@master with: image-ref: ${{ secrets.ECR_REGISTRY }}/api:${{ github.sha }} format: sarif output: trivy-results.sarif exit-code: 1 severity: CRITICAL ignore-unfixed: true - name: Upload scan results to GitHub Security uses: github/codeql-action/upload-sarif@v3 with: sarif_file: trivy-results.sarif
Use minimal base images. The fewer packages, the fewer vulnerabilities:
dockerfile# Before: fat image, 200+ CVEs FROM node:18 # After: minimal image, <10 CVEs FROM node:18-alpine # Even better: distroless FROM gcr.io/distroless/nodejs18-debian12
Gate 4: Infrastructure as Code Scanning
Your Terraform, Helm charts, and Kubernetes manifests may have security misconfigurations - public S3 buckets, overly permissive IAM policies, missing pod security contexts.
yamliac-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run Checkov IaC scan uses: bridgecrewio/checkov-action@master with: directory: . framework: terraform,kubernetes,helm soft_fail: false check: CKV_K8S_8,CKV_K8S_9,CKV_K8S_11,CKV_K8S_14,CKV_K8S_20
Key checks for Kubernetes:
- •
CKV_K8S_8- liveness probe defined - •
CKV_K8S_9- readiness probe defined - •
CKV_K8S_11- no privileged containers - •
CKV_K8S_14- image not using latest tag - •
CKV_K8S_20- containers not running as root
Runtime Security with Falco
The CI pipeline catches issues before deployment. Falco catches suspicious behaviour at runtime - in production.
bashhelm repo add falcosecurity https://falcosecurity.github.io/charts helm upgrade --install falco falcosecurity/falco \ --namespace falco \ --create-namespace \ --set falco.json_output=true \ --set falcosidekick.enabled=true \ --set falcosidekick.config.slack.webhookurl=$SLACK_WEBHOOK
Falco rules that matter:
yaml# Alert when a container writes to /etc - rule: Write below etc desc: Container wrote to /etc directory condition: > open_write and container and fd.name startswith /etc output: > Write to /etc in container (user=%user.name command=%proc.cmdline container=%container.id image=%container.image.repository) priority: WARNING # Alert when a shell is spawned in a container - rule: Terminal shell in container desc: A shell was used as the entrypoint or dropped condition: > spawned_process and container and shell_procs and proc.tty != 0 output: > Shell spawned in container (user=%user.name container=%container.id image=%container.image.repository shell=%proc.name parent=%proc.pname) priority: NOTICE
These rules detect common attack patterns: an attacker who gains code execution in your container will typically spawn a shell.
Secret Scanning
Before secrets reach the pipeline, catch them at commit time with pre-commit hooks:
bash# Install gitleaks brew install gitleaks # Add to .pre-commit-config.yaml repos: - repo: https://github.com/gitleaks/gitleaks rev: v8.18.0 hooks: - id: gitleaks
And scan the repository history for already-committed secrets:
bashgitleaks detect --source . --verbose
If secrets are found in history, rotate them immediately. Then use git filter-repo (not git filter-branch) to remove them from history. Assume they are already compromised.
The SOC2 / ISO 27001 Connection
These four gates map directly to SOC2 and ISO 27001 controls:
| Gate | SOC2 CC | ISO 27001 |
|---|---|---|
| SAST | CC6.1 - Logical and Physical Access | A.14.2 - Security in development |
| Dependency scan | CC6.1 - Vulnerability management | A.12.6 - Management of technical vulnerabilities |
| Image scan | CC6.1 - Change management | A.12.6 - Technical vulnerability management |
| IaC scan | CC6.6 - Network security | A.13.1 - Network controls |
| Runtime (Falco) | CC7.2 - Anomaly detection | A.12.4 - Logging and monitoring |
When your auditor asks "how do you detect vulnerabilities in your software?", your answer is a CI pipeline that blocks deploys on critical CVEs, documented and provable.
Getting Started
If you are starting from zero: implement in this order.
Week 1: Add Trivy dependency scanning to CI. Fix CRITICAL CVEs in your dependencies. Update your base images to current Alpine or Debian slim.
Week 2: Add Semgrep SAST with the secrets ruleset. Remediate any hardcoded credentials. Set up gitleaks pre-commit hook.
Week 3: Add IaC scanning (Checkov). Fix pod security contexts - drop root, add readOnlyRootFilesystem, set resource limits.
Week 4: Add container image scanning. Set up Falco for runtime.
Each week is independent. Week 1 alone eliminates most of what shows up in penetration tests.
Preparing for SOC2 or ISO 27001? Book a free audit - we will assess your current security posture and give you a gap analysis against the controls you need.