A scanner flags transformers, torch, vllm, or langchain with a fresh CVE and a scary number. The wrong move is to either patch everything reflexively or wave it off because “we’ll get it next cycle.” Both skip the only step that produces a defensible answer: figuring out whether your deployment can actually reach the vulnerable code with input an attacker controls. Here is a workflow for every ML-stack CVE, in order.
Step 0: Confirm the CVE is real and read the primary record
Before anything else, pull the NVD entry and read the description verbatim. You can also locate the entry and its sibling advisories for the same package through our ML Stack CVE Filter. This is not a formality. CVE IDs get rejected, disputed, or quietly re-scoped, and downstream feeds lag the source. IDs have circulated in blog posts that NVD lists as rejected. A disputed entry — common in ML, where “trusting your peers” is sometimes by design rather than a bug — changes the conversation entirely.
Capture from the primary record:
- Exact affected package and version range
- The CWE class (it predicts the mitigation; CWE-502 deserialization behaves nothing like a ReDoS)
- The CVSS vector, not just the number (attack vector, whether user interaction is required)
- Whether the status is anything other than “Analyzed/Published”
If the description does not match what your scanner claimed, trust the primary record.
Step 1: Classify the bug into an exposure family
ML-stack CVEs cluster into a small number of families, and the family tells you most of what you need:
| Family | Typical CWE | What it costs you | What gates it |
|---|---|---|---|
| Deserialization / load-time RCE | CWE-502 | Code execution on the host | Loading an untrusted artifact |
| Unsafe dynamic execution | CWE-94/95 | Code execution | A config flag or eval path |
| ReDoS / resource exhaustion | CWE-400/1333 | Worker hang, DoS | Attacker-controlled text through a specific path |
| Memory safety (native ops) | CWE-122/787 | Crash, sometimes worse | A crafted tensor through a specific kernel |
| Path traversal / archive | CWE-22/345 | File write, scan bypass | Extracting an untrusted archive |
The first two are the ones that justify dropping everything. The rest are real but almost always gated on a specific code path you may not even use.
Step 2: Read the patch, not just the advisory
The NVD entry rarely names the vulnerable function precisely enough. The fix commit always does. Open the GitHub Security Advisory (GHSA) if one exists, follow it to the fix PR, and note:
- The exact function or class that changed
- The public API that reaches it (this is what you grep your code for)
- The input shape that triggers it: a model file? a tokenizer string? a config JSON? an archive?
Fifteen minutes reading a diff routinely reverts a “critical, patch now” into “we never call that path,” and occasionally promotes a “medium” into “we call that on every request with user input — fix today.”
Step 3: Map it to your deployment
Now the only question that actually scores risk: does our pipeline reach the vulnerable path with bytes we did not produce and verify?
- Grep the codebase for the entry-point APIs from Step 2.
- For each hit, classify the input: trusted internal artifact, or untrusted (community checkpoint, user upload, runtime Hub pull, user free-text)?
- Check whether any required configuration flag is set (for example
trust_remote_code=True, an unsafe-load toggle, orweights_only=False). - Separate runtime inference from offline training/fine-tuning — a path that is safe in production may be exposed in your training pipeline, which is slower-moving but real.
- Check the artifact inventory, not only the code. If you cannot list the model files, adapters and framework versions in the deployment, build that record first: AIBOM formats and generators covers the tooling, and AI SBOM for ML models covers which fields belong in each entry.
The output is a single net-exposure rating: critical / high / medium / low / not applicable. “Not applicable” is a legitimate and common verdict, and reaching it deliberately is the whole point.
Step 4: Decide the action
Map exposure to action rather than mapping CVSS to action:
- Critical/High and reachable with untrusted input: patch now (pin to the fixed version), and audit what already passed through the path before the patch.
- Gated on a flag you set: disable the flag if you can; patch on the next cycle if you cannot.
- Medium and reachable only in training: schedule for the training-pipeline maintenance window.
- Not applicable: record why (which path, why unreachable) and patch on the normal cadence anyway, because “not applicable today” can become applicable when someone adds a feature.
Always record the reasoning, not just the verdict. The next person to see this CVE in a scanner should not have to redo the analysis.
Step 5: Add detection and a structural fix
Patching closes one instance; the family stays open. For each triaged CVE, ask whether a structural change retires the whole family:
- For deserialization CVEs, moving weights to
safetensorsremoves the sink, not just this entry. Which formats actually remove it and which only relocate it is set out in model file format security. - For unsafe-execution flags, defaulting them off across the org prevents the next one.
- For all of them, pinning artifacts by digest and scanning in CI catches the variant that has not been assigned a CVE yet. Picking the scanner is an ML vulnerability scanner comparison question; what a clean result on a checkpoint actually proves is covered in malicious checkpoint detection; and the signing, registry and AIBOM layers around it are in the AI supply-chain security tool comparison.
Then add a log signature for attempted exploitation where feasible, so a future attempt is visible rather than silent.
Feeding the verdict back into a standing process is what separates triaging one CVE from running a programme: the ML model CVE tracking workflow covers the feeds and the cadence. For a framework-specific worked example of the same steps, including two advisories in one library with different affected ranges, see TensorFlow and Keras CVE fixes.
A worked micro-example
A scanner flags transformers with a deserialization RCE.
- NVD: real, published, CWE-502, affects versions below a known fix line.
- Family: load-time RCE — the high-stakes bucket.
- Patch: the change is in a model/config loading path reached by
from_pretrainedagainst a repository. - Deployment map: we
from_pretrainedfrom a public Hub repo at startup → untrusted input, path reachable → high, reachable. - Action: pin to the fixed version now; audit which model revisions we have loaded.
- Structural fix: switch those models to safetensors and pull by digest so the next entry in this family is inert.
Total time: well under an hour, and the outcome is defensible to an auditor in a sentence.
Why the workflow beats the scanner
A vulnerability scanner produces a list sorted by a number computed against a generic threat model. This workflow produces a list sorted by your exposure, with a recorded reason for each verdict. The difference shows up the first time you can tell leadership “twelve flagged, three actually reachable, all three patched, here’s why the other nine aren’t urgent” — instead of either a nine-alarm fire drill or a shrug. Most teams run the scanner and call it done. The defensible work is everything after the scan.
Two shortcuts are worth knowing before you start. If the advisory records confirmed exploitation, step 3 is already answered for you — check AI CVEs in the CISA KEV catalog first. And if the advisory has no fixed version at all, the workflow’s output is a compensating control rather than a patch, which is the situation MLflow’s unpatchable advisory cluster puts most teams in.
See also
- Reading an ML library CVE beyond the CVSS score
- PyTorch security: notable CVEs and how to harden your loading path
- Hugging Face model supply chain risk: pickle backdoors and Transformers CVEs
- Inference server CVEs: vLLM, Ollama, llama.cpp, Triton
- Run the advisory against your own dependency list with the ML stack CVE filter
- Browse the full topic index of ML-stack CVE coverage
- Or narrow by subject from the tag index