Run Dependency Reviews in CI
Run dependency reviews in CI pipelines — Secure development.
Focus: run dependency reviews in ci pipelines
Your CI pipeline runs tests, lints, and builds — but what about the 400 dependencies you pulled from PyPI last week? One compromised or abandoned package can slip past every green checkmark and ship a vulnerability straight to production. That's the gap run dependency reviews in CI pipelines closes: a fast, automated security gate that fails the build before a vulnerable version ever merges.
The problem this lesson solves
Modern Python projects lean on dozens — often hundreds — of third-party packages. Each one is a tiny supply chain: it has authors, release processes, and dependencies of its own. Attackers know this. They publish typosquatted packages, compromise popular ones, or simply wait for a maintainer to stop patching. If you only audit dependencies occasionally, you will find out about a critical CVE from a news headline, not from your CI.
Manual reviews don't scale. A developer cannot eyeball every requirements.txt change, and vulnerability databases update faster than any human can follow. The result? A slow, inconsistent process that misses real risk. The pain is concrete: you deploy code that looks healthy but carries known-exploitable libraries.
The fix is to institutionalize the review. You make dependency checking a first-class CI job that runs on every push, every pull request, and every release. It gives you instant feedback, a clear audit trail, and a guardrail that enforces your security policy without relying on memory. This lesson shows you exactly how to build that gate.
Core concept / mental model
Think of dependency reviews like a customs check at an airport. Your code is a traveler arriving from a foreign country — the package ecosystem. Every dependency is a suitcase. The customs officer (your CI job) inspects each suitcase against a list of known contraband (the vulnerability database). If something is flagged, the traveler doesn't enter the plane (the build fails) until the issue is resolved.
The officer doesn't search every item every time. They rely on maintained blacklists, fresh intelligence, and a rules engine. Your CI does the same: it queries a vulnerability feed, compares versions against known-bad ranges, and reports findings with severity scores.
Here are the core definitions you need:
- Dependency review — the automated process of comparing your project's dependency tree against a database of known vulnerabilities and policy rules.
- Vulnerability database — a catalog of CVEs and platform-specific advisories (NVD, OSV, PyPI's own feed).
- SBOM — a Software Bill of Materials; a machine-readable inventory of every library and version you ship.
- CI gate — a job in your pipeline that can fail the build, not just warn.
The mental model has three layers: inventory (know what you have), scan (compare against known issues), enforce (decide what happens on findings). Your pipeline must cover all three. Skipping enforcement turns the review into a report nobody reads; skipping inventory leaves you blind to transitive dependencies.
How it works step by step
Implementing dependency reviews in CI follows a repeatable sequence. Here is the end-to-end flow, from a code change to a gate decision.
- Define your policy. Decide what severity blocks a merge. Common defaults: fail on critical and high, warn on moderate, ignore low. Also set a rule for packages with no fix available — you may want to alert but not break the release.
- Collect the dependency tree. Your package manager generates a lock file (
requirements.txt,Pipfile.lock, orpoetry.lock) during install. That lock file is your inventory — commit it to version control so the review is deterministic. - Pick a scanner. Options include
pip-audit(Python-native, free),OSV-Scanner(aggregates multiple databases),Safety(commercial feed), or GitHub's built-in Dependabot. Each has different coverage and speed trade-offs. - Add a CI job. The job runs before final merge, after tests. It installs the scanner, points it at the lock file, and captures the exit code.
- Enforce the gate. If the scanner finds findings above your threshold, the job exits non-zero. Your CI system marks the build as failed, blocking the merge or deployment.
- Report and alert. Print a readable summary in the job log or attach it as an artifact. For pull requests, post a comment so developers see the issue without digging into logs.
- Review exceptions. When a vulnerability has no fix, allow a temporary ignore with a ticket reference. Log that exception so it expires and the issue resurfaces.
Pro tip: Run the review on every commit, but allow fast fail on trivial changes. You can gate only on the diff of changed dependencies for speed, then do a full scan nightly for deep coverage.
The system is only as good as your policy and your ability to act on results. A scanner that fails randomly will be ignored. A scanner that never fails is theatre. Calibrate carefully.
Hands-on walkthrough
Let's build a working example. We'll use pip-audit because it is free, fast, and integrates naturally with Python projects.
Step 1: Pin your dependencies
Start with a lock file. If you use pip, generate a full frozen list:
# Freeze current environment into requirements.txt
pip freeze > requirements.txt
For reproducibility, prefer pip-tools:
pip install pip-tools
pip-compile requirements.in -o requirements.txt
Now requirements.txt contains exact versions — the inventory your scanner will read.
Step 2: Run pip-audit locally
Test the scanner before wiring it into CI:
pip install pip-audit
pip-audit -r requirements.txt
Expected output (clean):
No known vulnerabilities found
If you have a vulnerable package, you'll see a table like:
Found 2 vulnerabilities in 1 package
Name Version ID Fix
requests 2.31.0 CVE-2024-1234 upgrade to 2.32.0
Step 3: Write a CI script
The script below runs an audit and fails on critical or high findings:
#!/bin/bash
set -e # fail on error
pip install pip-audit
pip-audit -r requirements.txt --fail-on critical --fail-on high
Save it as scripts/dependency_review.sh and run it in your pipeline.
Step 4: Add a GitHub Actions job
Here is a complete workflow file that runs on every push and pull request:
name: CI
on: [push, pull_request]
jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: pip install -r requirements.txt
- name: Run dependency audit
run: |
pip install pip-audit
pip-audit -r requirements.txt --fail-on critical --fail-on high
When a new CVE is published, your next push will fail with a clear message. Merge is blocked until you upgrade or file an exception.
Step 5: Test with a real vulnerability
To verify the gate works, intentionally use an old, known-vulnerable version:
# requirements.txt (for testing)
flask==0.12.2 # old, has known vulns
Run pip-audit -r requirements.txt. You'll see findings and exit code 1, which fails the CI job — exactly what you want.
Compare options / when to choose what
Different scanners fit different needs. Here is a quick decision table:
| Tool | Coverage | Integration | Cost | Best for |
|---|---|---|---|---|
pip-audit |
PyPI, OSV | CLI, GitHub Action | Free | Python-only, fast CI |
OSV-Scanner |
OSV (broad ecosystem) | CLI, GitHub Action | Free | Multi-language, SBOM scanning |
Safety |
Commercial DB | CLI, API | Paid | Enterprise policy, detailed audit |
| Dependabot | GitHub native | PRs, npm/pip/other | Free (GitHub) | Automated fix PRs, tight GitHub |
For most Python teams, start with pip-audit. It is simple, free, and catches the majority of issues. If you need cross-language scanning or SBOM export, add OSV-Scanner. Enterprise clients often prefer Safety for its curated feeds and compliance reports. Dependabot complements any scanner by proactively opening upgrade PRs; use it alongside a CI gate.
Trade-off: Scanning after merge (nightly) gives developers more freedom but risks a vulnerable release. A pre-merge gate is stricter but can cause friction on urgent hotfixes. Choose based on your release velocity and risk tolerance.
Troubleshooting & edge cases
Dependency reviews in CI fail for surprising reasons. Here are the common ones and their fixes.
False positives on transitive dependencies. A package you use directly might be safe, but a child dependency is flagged. Audit tools report the full tree. Solution: review the actual usage — if the vulnerable code path is never exercised, you can add an ignore with justification. But be careful: “not used” is often wrong.
Build fails because pip-audit can't resolve your lock file. If you use poetry.lock, pip-audit -r won't work. Use pip-audit --path . or export to a requirements file. Similarly, if you use a custom index, set --index-url so the tool can discover versions.
Scanner times out in CI. Auditing hundreds of packages takes time, especially with many shared dependencies. Mitigate by caching the scanner and the audit cache, or split by package groups. You can also skip the full scan on trivial changes and run a targeted diff.
False negatives because the database is stale. The OSV and NVD feeds update continuously. If your CI caches too aggressively, you may miss new advisories. Force a refresh weekly, or run a nightly full scan as a complement to the per-commit quick check.
Cycle of ignored vulnerabilities. You add an ignore because “no fix exists.” Months later, a fix appears, but the ignore stays. Solution: make ignores expire (e.g., --expires 90d or a ticket) and train the team to re-evaluate.
Exit codes and shell pitfalls. In bash, a failing command in a set -e script stops execution — which is what you want. But if you have multiple scanners, capture the first failure and still run all of them, then combine results. Otherwise you might only see one issue at a time.
What you learned & what's next
You now understand run dependency reviews in CI pipelines from practice. You can explain why manual reviews fail, build a dependency review gate with pip-audit and GitHub Actions, compare tools like OSV-Scanner and Dependabot, and troubleshoot the most common edge cases — from lock file mismatches to false positives. That directly achieves the lesson's learning objectives: you've explained the core idea and completed a working exercise.
Next in the Secure development track, you'll move beyond dependencies to running secret scanning in CI — catching leaked API keys and tokens before they hit the repo. The same mental model applies: automate the security check, make it a hard gate, and integrate it into your everyday workflow. With dependency reviews and secret scanning combined, your pipeline becomes a genuine security barrier, not a paperwork exercise.
Practice recap
Now try it yourself: create a throwaway repo with a simple requirements.txt containing flask==0.12.2, run pip-audit -r requirements.txt, and confirm the exit code is non-zero. Then update to a patched version and re-run to see the green state. This exercise cements the cause-and-effect of a CI dependency gate.
Common mistakes
- Not committing a lock file — without exact versions, audits are non-reproducible and may miss the exact vulnerable release.
- Failing on every finding without considering severity, which blocks legitimate hotfixes and trains developers to ignore the gate.
- Ignoring transitive dependencies — only reviewing direct packages leaves the majority of the attack surface unexamined.
- Using stale vulnerability feeds because of aggressive caching in CI, leading to false confidence and missed new CVEs.
Variations
- Use OSV-Scanner instead of pip-audit for multi-language projects and SBOM export.
- Adopt Dependabot to auto-open upgrade PRs, complementing your CI audit.
- Run a nightly full dependency scan in a scheduled cron job, in addition to per-commit quick checks.
Real-world use cases
- A Python web service with 150 packages uses a CI gate that fails on critical CVEs, catching Log4Shell-like issues before release.
- A data engineering team uses OSV-Scanner in a scheduled job to keep an SBOM current for a SOC 2 audit.
- A startup with a fast release cadence uses Dependabot PRs plus a pip-audit gate to upgrade daily without manual reviews.
Key takeaways
- Dependency reviews are an automated, policy-based CI gate that checks your dependency tree against known vulnerabilities.
- You need an inventory (lock file), a scanner, and a decision rule — inventory → scan → enforce.
- Set severity thresholds (fail on critical/high) to avoid blocking important work with noise.
- pip-audit is a free, fast, Python-native scanner; OSV-Scanner covers more ecosystems; Dependabot automates fixes.
- Test your gate with a deliberately old version to verify it fails as expected.
- Handle exceptions with expiring ignores and write a ticket so stale exceptions don't hide real risk.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.