ML model serving environments are dependency nightmares. You pull in PyTorch, Transformers, NumPy, Pillow, and suddenly you have 200+ transitive dependencies – any one of which could have a known CVE sitting in production. Most teams never check. Here’s how to build a scanner that catches these before they become incidents.
| |
That’s the fastest path to scanning your environment. pip-audit checks every installed package against the Python Packaging Advisory Database (PyPA) and the OSV database. The JSON output gives you structured data to build automation around.
Scanning ML-Specific Packages
ML dependencies are particularly risky because they ship native code, bundle C++ libraries, and often lag behind security patches. A typical model serving requirements.txt looks like this:
| |
Run the scan against it:
| |
The --desc on flag includes vulnerability descriptions so you can triage without looking up every CVE manually. You’ll often find issues in Pillow (image parsing bugs are a recurring theme), NumPy (buffer overflows in older versions), and occasionally in torch’s bundled libraries.
For a broader check that includes the safety database as well, install both tools:
| |
Using both gives you better coverage. pip-audit pulls from PyPA/OSV, while safety uses its own curated database. Some CVEs show up in one but not the other.
Building a Scan Report Script
A one-off scan is fine. But you want something that runs in CI, produces a clear pass/fail, and generates a report your team can act on. Here’s a Python script that wraps pip-audit, parses the JSON output, and generates a summary:
| |
Run it locally:
| |
The script exits with code 1 when vulnerabilities are found, which makes it plug directly into any CI system as a gate.
Integrating with GitHub Actions
The real value comes from running this on every PR and blocking merges when new vulnerabilities appear. Here’s a GitHub Actions workflow that does exactly that:
| |
The || true on the individual tool runs prevents them from short-circuiting the workflow. The custom scanner script handles the final pass/fail decision. The schedule trigger runs a weekly scan even when nobody is pushing changes – new CVEs get published against existing versions all the time.
Scanning Multiple Requirements Files
You probably have multiple requirements files: requirements.txt for serving, requirements-dev.txt for training, maybe requirements-test.txt for CI. Scan all of them:
| |
Pinning to Safe Versions
When pip-audit finds a vulnerability, it tells you which versions contain the fix. Use pip-audit --fix to automatically update your requirements file to the nearest safe version:
| |
Always run with --dry-run first. The automatic fix picks the closest safe version, but in ML land, jumping a major version of PyTorch or NumPy can break your model loading code. Review the suggested changes, test your model inference, then apply:
| |
For a more controlled approach, build a script that generates a pinned requirements file with comments explaining why each pin exists:
| |
This gives you a clear audit trail. When someone asks “why is Pillow pinned to 10.2.0?”, the comment says exactly which CVEs drove the decision.
Common Errors and Fixes
pip-audit fails with “No matching distribution found”
This happens when your requirements file includes packages that aren’t installed in the current environment. Either install them first or scan the live environment instead:
| |
Running pip-audit without -r scans the currently installed environment instead of a requirements file. This avoids the resolution problem entirely.
safety returns “Invalid API key”
The free tier of safety works without an API key for basic checks. If you see auth errors, make sure you’re not setting SAFETY_API_KEY to an expired token. Remove the env var and it falls back to the free database:
| |
GPU-only packages fail to resolve
PyTorch with CUDA dependencies can’t resolve on a CPU-only CI runner. Use the CPU index URL in your CI requirements:
| |
Or maintain a separate requirements-ci.txt that uses CPU-only torch builds for scanning purposes.
Transitive dependency vulnerabilities
pip-audit catches transitive dependencies by default when scanning an installed environment. But when scanning a requirements file with -r, it only checks what’s listed. For full transitive coverage, install first, then scan:
| |
This catches vulnerabilities in packages you never explicitly listed but that got pulled in by torch or transformers.
False positives on yanked versions
Sometimes pip-audit flags a version as vulnerable because it was yanked from PyPI, not because of a CVE. Check the --ignore-vuln flag to suppress known false positives:
| |
Keep a .pip-audit-ignore file in your repo to track suppressed findings with justifications so your team knows why each was dismissed.
Related Guides
- How to Build a Model Batch Inference Pipeline with Ray and Parquet
- How to Build a Model Input Validation Pipeline with Pydantic and FastAPI
- How to Build a Model Configuration Management Pipeline with Hydra
- How to Build a Model Metadata Store with SQLite and FastAPI
- How to Build a Model CI Pipeline with GitHub Actions and DVC
- How to Build a Model Serving Pipeline with Ray Serve and FastAPI
- How to Build a Model Canary Analysis Pipeline with Statistical Tests
- How to Build a Model Deployment Pipeline with Terraform and AWS
- How to Build a Model Health Dashboard with FastAPI and SQLite
- How to Build a Model Feature Store Pipeline with Redis and FastAPI