Every production pipeline needs three layers of automated scanning. Skip one, and you will spend hours in incident retrospectives explaining "why didn't we catch this earlier?".
Three minimum layers — and why
| Layer | Detects | Common tools |
|---|---|---|
| SAST | Source code flaws (SQLi, XSS, path traversal, insecure deserialization) | Semgrep, CodeQL, SonarQube |
| SCA | CVEs in dependencies, license violations | Trivy, Grype, Snyk, Dependency-Track |
| Secret scan | API keys, tokens, private keys accidentally committed | Gitleaks, Trufflehog, GitHub secret scanning |
SAST: start with Semgrep
Semgrep is easy to enable, has a wide open-source ruleset and rules read like real code. Minimal GitHub Actions workflow:
name: semgrep
on:
pull_request:
push:
branches: [main]
jobs:
semgrep:
runs-on: ubuntu-latest
container: returntocorp/semgrep
steps:
- uses: actions/checkout@v4
- run: semgrep ci --config=p/owasp-top-ten --baseline-ref=origin/main
Two important tips:
- Baseline diff scan: only block new findings on PRs; old findings go to a backlog with owners. This avoids wholesale build breakage when you first turn it on.
- Custom rules: write 5-10 rules for internal patterns (e.g., logging secrets, calling internal APIs without the wrapper client). That is where unique value comes from.
SCA and SBOM with Trivy/Grype + Syft
Generate a CycloneDX SBOM and scan for CVEs in the same pipeline:
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
format: cyclonedx-json
output-file: sbom.cdx.json
name: Scan SBOM with Grype uses: anchore/scan-action@v3 with: sbom: sbom.cdx.json fail-build: true severity-cutoff: high
Push the SBOM into OWASP Dependency-Track to monitor the long tail: when a new CVE drops, the dashboard automatically lists impacted projects. A reference SLA:
- CRITICAL with available fix: patch within 7 days.
- HIGH with available fix: patch within 30 days.
- MEDIUM/LOW: review quarterly, accept or backlog.
Secret scanning: pre-commit + CI + repo-wide
- Pre-commit hook with
gitleaks protect: blocks at the developer's local commit. - CI scan on every PR: catches anything that bypassed the hook or was committed via the web UI.
- Periodic repo-wide history scan: surfaces old secrets still living in git log.
When a leaked secret is found:
- Step 1 — Treat it as compromised: rotate the key/token immediately, do not rely on history rewrites.
- Step 2: audit usage (CloudTrail, Vault audit log) for anomalous activity.
- Step 3: clean history with BFG or
git filter-repoif the repo is public. - Step 4: add a pre-commit rule so the same pattern can't recur.
Managing secrets the right way
- Vault / AWS Secrets Manager / Azure Key Vault with dynamic credentials for DBs and queues.
- Mozilla SOPS to encrypt secret files in git, with the key in KMS — fits GitOps.
- OIDC federation between GitHub Actions and the cloud, eliminating long-lived access keys.
Beware: alert fatigue
- Enable baseline mode: only block new findings.
- Start with high severity (CRITICAL/HIGH), lower the bar over time.
- A triage owner dispatches findings to the right team within 24h.
- The MTTR-by-severity dashboard is the only KPI — not the count of scans run.
Conclusion
SAST + SCA + Secret scanning is the cheapest and highest-leverage baseline you can enable this week. Start small — one repo, one ruleset, one pre-commit hook — measure MTTR, then expand. After 3-4 months these three layers stop most basic defects, freeing the security team to focus on threat modeling, supply chain and runtime.
