Most of your code is other people's code. Scan third-party dependencies (SCA) and container images for known CVEs on every build, fail on fixable highs, and keep the noise down with policy.
A typical app is a thin layer of your code over a deep stack of open-source packages and a base OS image full of libraries. Known vulnerabilities (CVEs) in any of them are your vulnerabilities. Software Composition Analysis (SCA) inventories those components and matches them against vulnerability databases — this is how you learn a transitive dependency five levels down has a critical RCE.
What actually runs in prod:
your code ~2% (SAST covers this)
direct deps ~8%
transitive deps ~40% <- SCA covers these
base image OS libs ~50% <- image scanning covers these
SCA answers: "which of the components we ship have known CVEs,
and is there a fixed version?" The Supply Chain Security course
goes deeper (SBOM, signing, provenance); here we wire the gate.Scan the dependency manifest and lockfile against CVE databases on each pull request. Trivy scans many ecosystems (npm, pip, Go, Maven...) from the filesystem; language-native tools (npm audit, pip-audit) work too. The key is to fail on fixable, high-severity issues — a CVE with a patch available is an action item, not just information.
# job: deps — Software Composition Analysis
deps:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Trivy filesystem (dependencies)
uses: aquasecurity/trivy-action@0.24.0
with:
scan-type: fs
scan-ref: .
severity: HIGH,CRITICAL
ignore-unfixed: true # only fail on CVEs with a fix available
exit-code: "1" # fail the PR
format: sarif
output: trivy-deps.sarif
- uses: github/codeql-action/upload-sarif@v3
with: { sarif_file: trivy-deps.sarif }Even with clean dependencies, your base image ships an OS with its own vulnerable packages. Scan the built image so you catch those before it reaches a registry or cluster. Combine this with a minimal base (distroless, alpine) to shrink the surface — image hardening is covered fully in the Container & Kubernetes Security course.
# job: image-scan — runs after the image is built
image-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t app:${{ github.sha }} .
- name: Trivy image scan
uses: aquasecurity/trivy-action@0.24.0
with:
scan-type: image
image-ref: app:${{ github.sha }}
severity: HIGH,CRITICAL
ignore-unfixed: true
exit-code: "1"
# tip: a distroless/alpine base cuts most of these findings.Scanning finds problems; automated updates fix them. Dependabot or Renovate open pull requests that bump vulnerable dependencies, which your pipeline then tests automatically — turning patching from a chore into a review. Pair with a policy file so you can consciously accept a specific CVE (with an expiry) rather than disabling the whole gate.
# .github/dependabot.yml — auto-open PRs to patch vulnerable deps
version: 2
updates:
- package-ecosystem: npm
directory: "/"
schedule: { interval: weekly }
open-pull-requests-limit: 10
# .trivyignore — accept a specific CVE deliberately, with a reason + expiry
# CVE-2024-12345 exp:2026-01-01 false positive: vulnerable code path not reached
CVE-2024-12345
# "accept" is a decision, not a bypass: it's in the repo and reviewable.