Supply Chain Attacks
- Source
- 11-supply-chain.md
- State
- Editorial review
- Edition
- 2026-draft
- Estimated reading time
- 22 min
Draft chapter under editorial review
This material is available for early reading, but it has not reached the reviewed 1.0 release. Technical references, examples, and wording may change.
AI systems depend on more third-party components than any other software category. A single production LLM application typically pulls in a base model (from a model hub), fine-tuning data (from a public corpus or a data broker), pretrained embeddings, a dozen framework dependencies (torch, transformers, langchain, openai, pydantic), containerized runtime images, plus the tooling ecosystem — MCP servers, guardrail libraries, monitoring agents, vector store clients. Every dependency is a supply-chain surface. Compromise one and reach every downstream deployment.
This chapter has been substantially expanded from prior editions. The scale of the problem is no longer theoretical: security researchers have identified roughly 100 malicious models hosted on a major public model hub, a steady cascade of scanner-bypass CVEs against the most widely deployed pickle scanner, confirmed backdoored packages in the Model Context Protocol ecosystem, and two independent academic studies confirming that different LLMs hallucinate an overlapping, predictable, and still partially exploitable set of package names. The chapter covers model-artifact poisoning and distribution attacks, the full picklescan/Fickling CVE cascade, model-format-specific vulnerabilities (safetensors, GGUF, Keras, ONNX), LoRA/adapter poisoning, tokenizer manipulation, dependency-tree compromise, MCP-server backdoors and typosquats, and slopsquatting — closing with a concrete artifact-verification checklist.
11.1 Attack surface inventory
The supply chain touchpoints:
Model artifacts. .pt, .safetensors, .pkl, .h5, .keras, .gguf, .onnx files distributed from Hugging Face, private model registries, cloud model catalogs, and vendor CDNs. Chapter 09 covered LSB steganography and pickle __reduce__ mechanics inside these artifacts; this chapter extends to distribution-side manipulation and format-specific vulnerabilities.
Fine-tuning data corpora. Instruction-tuning datasets, RLHF preference sets, domain-specific corpora curated by the target's data team or bought from vendors.
Dependency chains. Python packages, npm packages, container base images, wheels, Docker Hub tags. Standard software supply chain attack surface with AI-specific twists, including LLM-hallucinated package names (Section 11.9).
MCP server implementations. The tool servers agents talk to. Source distributed via npm/PyPI/git. A compromised MCP is direct action authority over every client.
Tokenizers and vocabularies. JSON files shipped alongside models. Vocabulary manipulation changes model behavior without touching weights.
Adapters. LoRA files, prompt tuning artifacts, low-rank specializations. Smaller and less scrutinized than base models.
Guardrail rules. Regex libraries, classifier weights, denylists. If a guardrail depends on a data file loaded at startup, that data file is an injection surface.
Evaluation datasets. Public benchmarks the target uses for internal quality assurance. Poisoning benchmarks steers the target's model-selection decisions.
For the worked examples in this chapter, assume the target is northstar-agent, a support assistant deployed by Northstar Labs at northstar.example that pulls a base model and a domain-specialized LoRA adapter from a public model hub, uses an MCP server for ticket lookups, and has an internal coding-agent workflow that occasionally installs packages an LLM suggests.
11.2 Model-artifact distribution attacks
The model hub ecosystem (Hugging Face, Ollama registry, ML models on S3 mirrors) has grown faster than its security review. Practical attack vectors:
Namespace typosquatting. Publish a model with a name close to a legitimate popular one: sentence-transformers-mini, all-MiniLM-L6-V2 (capital V), openai-embedding-ada-002-official. Users copy-pasting model names hit the malicious variant.
Repository hijacking. Compromising a maintainer account. Model hubs' account security is a spot check; MFA is often optional. Once the account is compromised, all published models are effectively attacker-controlled.
Fork abuse. Fork a popular model into an attacker's namespace with a "specialized" or "quantized" label, insert payload, promote via SEO or by claiming affiliation.
Repository poisoning of legitimate maintainers. Contribute a "helpful improvement" PR that carries a payload; if merged, the change is republished.
Registry poisoning. For self-hosted model registries (MLflow, SageMaker, Vertex), compromise the registry's storage backend and overwrite artifact files. The registry's metadata still points to the same version, but the file has changed.
11.2.1 Confirmed cases: JFrog's ~100 malicious models
JFrog Security Research's audit of publicly hosted models on Hugging Face found approximately 100 models carrying genuinely malicious payloads (not false positives) — predominantly PyTorch pickle files abusing the __reduce__ method to execute arbitrary code on deserialization. One well-documented example, published under the account name baller423, established a reverse shell to a hardcoded IP address in the KREONET (South Korean research network) address range. Other variants used Keras Lambda layers and the runpy module specifically to evade scanner detection.
In March 2025, JFrog announced a scanning integration directly with Hugging Face ("File Security Scans"), reporting that its methodology eliminates 96% of the false positives produced by prior scanners while still catching threats other scanners miss — in the same rollout, JFrog identified 25 "zero-day malicious" models that no other scanner on the platform had flagged. In a 2025 interview, JFrog also disclosed having found 20 CVEs across MLOps platforms overall, including an XSS-to-RCE chain in Jupyter-based environments.
Model jacking. JFrog coined this term for the practical impact of the above: hijacking publicly hosted ML models by inserting malicious payloads that execute on load, granting persistent shells or backdoors with access to the victim's repository access tokens and datasets, and potentially enabling both trade-secret theft and downstream training-data poisoning of anything the compromised host subsequently produces.
11.2.2 nullifAI: evading picklescan via 7z compression
ReversingLabs researcher Karlo Zanki discovered two malicious models on Hugging Face in February 2025 using a technique the researchers named nullifAI, explicitly engineered to evade existing safeguards:
- Repositories:
glockr1/ballr7andwho-r-u0000/0000000000000000000000000000000000000. - The models were PyTorch format (compressed pickle), but instead of the standard ZIP compression PyTorch uses by default, they used 7z compression.
- This let the files fly under the radar of picklescan, the tool Hugging Face uses to detect suspicious pickles, because picklescan expected the standard ZIP format.
- The malicious payload — a platform-aware reverse shell with a hardcoded IP — sat at the beginning of the pickle stream; deserialization was deliberately broken immediately after the payload executed, producing a silent failure that made the file look corrupted rather than malicious.
- These are believed to be proof-of-concept uploads rather than an active supply-chain campaign.
The lesson for testers: a scanner's assumptions about container/compression format are themselves part of its attack surface, independent of anything it knows about pickle opcodes.
11.3 The stealthy backdoor pattern
A well-designed backdoor in an ML supply chain has three properties:
- Correct behavior on almost every input — the model passes standard evaluation and manual testing
- Attacker-triggered behavior on specific inputs — a trigger word, a pattern, a class, or a fingerprint
- Evasion of common detectors — no obvious anomalies in weight distributions, no suspicious dependency imports, no unusual runtime allocations
Combining Chapter 09's data-level trojan with a delivery mechanism that survives model-file scanners is the practical pattern:
- Train the backdoor into the model via trojan poisoning (Chapter 09), optionally using the harmless-data-only construction (Section 9.4.4) to survive content-based review.
- Distribute the artifact through a channel the target trusts (fork, mirror, quantization drop).
- Include a benign-looking "helper" script that the target's loader will execute — for weight-format loaders (safetensors), this often lives in a companion
utils.pyorpreprocessor_config.json, or is enabled outright bytrust_remote_code=True. - Trigger extraction and post-exploitation via the backdoored behavior once the target model is deployed.
For pickle-format artifacts, the stealth is different: the payload lives in the pickle bytecode, and the primary problem is bypassing the scanner — the subject of Section 11.4.
11.4 Pickle scanner bypasses: the full CVE cascade
Pickle scanners (picklescan, model-hub built-in scanners, EDR pickle inspectors, modelscan) look for known-bad gadgets — os.system, eval, exec, subprocess, __import__. Chapter 09, Section 9.6.1 covered the __reduce__ mechanism itself. This section gives the complete, current picture of how that denylist approach has failed in practice.
11.4.1 The structural problem: denylist versus allowlist
picklescan (mmaitre314/picklescan), the scanner Hugging Face uses internally, works by maintaining a list of known-dangerous globals (modules and callables) and flagging any pickle stream that references one. This is a denylist architecture, and it has a structural weakness: it can only flag gadgets someone has already thought to add to the list. Every CVE below is a different, previously uncataloged gadget that fell outside the denylist — pty.spawn was not flagged because nobody had added pty to the list; numpy.f2py internals were not flagged because the path-prefix check that should have blocked the whole module had a logic bug; and so on. The lesson generalizes past picklescan specifically: any scanner built on enumerating known-bad callables will keep failing against gadgets nobody has cataloged yet, because the number of dangerous callables reachable from a typical Python/ML dependency tree vastly exceeds any hand-maintained list.
11.4.2 CVE table: picklescan bypass cascade, 2025-2026
| CVE / advisory | Gadget / root cause | Mechanism |
|---|---|---|
| CVE-2025-71325 (GHSA-9gvj-pp9x-gcfr, CVSS 9.3) | _list_globals parsing logic error on STACK_GLOBAL opcodes | The function scans arguments in range 1 to n-1 instead of 0 to n-1. Placing the dangerous argument at position 0 triggers an unhandled exception that the scanner silently absorbs, returning a "clean" verdict instead of flagging the file. Also affects modelscan and Hugging Face's online scanners. |
| CVE-2025-71321 (GHSA-hgrh-qx5j-jfwx, CVSS 8.8) | pty.spawn not on the dangerous-globals list | pty (and specifically pty.spawn) was never added to the unsafe-globals denylist, so picklescan marks it "suspicious" but not "dangerous," allowing it to load. Discovered via the HeroCTF 2025 "Irreductible 2" challenge. |
| CVE-2025-71322 (GHSA-6556-fwc2-fg2p) | numpy.f2py.crackfortran._eval_length / getlincoef | Blocking the entire numpy.f2py module ("numpy.f2py": "*") failed because _build_scan_result_from_raw_globals did not correctly check every hierarchical prefix of the module path (for a.b.c, it must check both a and a.b). |
| CVE-2025-71373 (GHSA-x843-g5mx-g377, CVSS 8.1) | operator.methodcaller | picklescan does not detect calls to operator.methodcaller, allowing arbitrary code execution disguised as a call to that function. |
| CVE-2025-71321 (VulnCheck advisory, distutils variant) | distutils.file_util.write_file | Denylist bypass allowing arbitrary file writes, enabling overwrite of critical files for denial-of-service or downstream RCE. |
| CVE-2025-10156 | Malformed ZIP / CRC handling | If a file inside the ZIP container has an invalid CRC checksum, picklescan errors out and returns no result at all rather than treating the file as suspicious. PyTorch often disables CRC verification, so it loads the file anyway. |
All of the above were fixed in picklescan 0.0.33 (several already in 0.0.26/0.0.27). Note the pattern shared with nullifAI (Section 11.2.2): several of these bypasses (malformed ZIP/CRC, silent exception handling) are container-format or error-handling gaps rather than pickle-opcode gaps — the same class of weakness, independently discovered.
11.4.3 Fickling's allowlist and its own bypass: CVE-2026-14535
Trail of Bits' Fickling took a different architectural approach in 2025, introducing an allowlist mode (MLAllowlist) that blocks any import outside a known-good ML ecosystem (torch, numpy, transformers, and similar), complementing its traditional denylist pass (UnsafeImportsML). This is a meaningfully stronger design than picklescan's pure denylist — but even an allowlist is only as good as its implementation.
CVE-2026-14535 (GHSA-mgx3-9w7v-8674, high severity) affects Fickling versions up to and including 0.1.11. The UnsafeImportsML pass unconditionally calls AnalysisContext.shorten_code(node) on every import it examines, recording the code in a shared set called reported_shortened_code. When the MLAllowlist pass runs afterward on the same imports, it receives already_reported=True for all of them and executes a continue — completely skipping its own allowlist check. The result: any standard-library module not already in the denylist can be invoked via pickle deserialization, and fickling.load() returns LIKELY_SAFE for a payload that actually executes code. Fixed in Fickling 0.1.12.
The structural lesson here compounds the one from Section 11.4.1: even a well-designed allowlist can be defeated by shared mutable state between analysis passes — a bug class that has nothing to do with Python security semantics and everything to do with ordinary software engineering discipline in the scanner's own codebase. Layered scanning (Section 11.10) should assume any single scanner, denylist or allowlist, will eventually have exactly this kind of bypass.
11.4.4 Combining with tensor steganography
As covered in Chapter 09, Section 9.6.4, even under weights_only=True — which blocks pickle bytecode execution entirely — the tensor data still loads. LSB-encoded payloads inside weights survive that restriction and require a separate extraction stager, commonly delivered as a companion Python file the target imports as part of the model package.
11.5 Model formats beyond pickle: safetensors, GGUF, Keras, ONNX
11.5.1 Safetensors: the number-one mitigation, not a silver bullet
safetensors (Hugging Face) was designed explicitly to prevent code execution during deserialization, unlike pickle, and is the number-one recommended mitigation against pickle-style payloads. It does not, however, eliminate every vector: malicious metadata fields, corrupted tensors that crash or exploit the parser (denial of service), or — most importantly for a pentester — a repository that pairs a fully benign .safetensors file with arbitrary Python code (modeling_*.py, or a config.json that sets trust_remote_code=True) that executes malicious logic through the transformers loading path regardless of how safe the tensor file itself is. Verifying "this repo uses safetensors" is a necessary check, not a sufficient one — always also check whether the loader will execute any bundled Python.
11.5.2 GGUF (llama.cpp): CVE-2025-53630
GHSA-vgg9-87g3-85w8 / CVE-2025-53630 is an integer-overflow vulnerability (CWE-122, CWE-680) in llama.cpp's GGUF parser that produces out-of-bounds heap reads/writes. The function computing the cumulative size of tensors does not validate for overflow when summing individual tensor sizes, so a crafted GGUF file (with falsified tensor names, dimensions, types, and offsets in its metadata) can produce an incorrect ctx->size value that passes validation. Reading, quantizing, or printing the tensor data then triggers the out-of-bounds access, leading to information disclosure or memory corruption. A public proof-of-concept file (overflow_poc.gguf) is hosted on Hugging Face.
11.5.3 Keras .keras / .h5: CVE-2025-9905, a safe_mode bypass
CVE-2025-9905 shows that Keras's Model.load_model can be exploited for arbitrary code execution even with safe_mode=True enabled — the setting specifically meant to prevent this class of attack. The exploit uses a specially crafted .h5/.hdf5 file that leverages the Keras Lambda layer, which supports arbitrary Python code serialized via pickle. The root cause: safe_mode=True is not enforced when reading .h5 files, because that legacy format is only supported in Keras 3 for backward compatibility. This confirms that the legacy .h5 format remains an active attack surface even in modern Keras 3 deployments that primarily use the newer, safer native .keras format — testers should check which format a target actually accepts at load time, not which format the documentation recommends.
11.5.4 ONNX custom operators
ONNX models can incorporate custom operators that execute arbitrary native code (C++/CUDA) at load time in runtimes like ONNX Runtime, if an attacker gets the victim to load a malicious custom-operator library alongside the model. ONNX itself is a serialized computation-graph format (protobuf) with no code execution by default, unlike pickle — but the surrounding extension ecosystem (custom ops, conversion plugins from PyTorch/TensorFlow) reintroduces the same class of risk whenever untrusted models are combined with runtimes that load external libraries without validating their origin.
11.6 LoRA and adapter poisoning
Low-rank adapters (LoRA) are small delta weights that specialize a base model to a domain (medical, coding, safety-tuned). Adapter format is efficient — a base 7B model plus a domain-specific LoRA is roughly 200 MB rather than the full 14 GB fine-tune. Distribution channels for adapters are less mature than for base models: often ad-hoc Hugging Face repositories, personal drop-boxes, GitHub releases, and — as the following research shows — a genuinely under-scrutinized "share-and-play" ecosystem where adapters are combined without centralized verification.
Basic attack shape. Fine-tune a LoRA against a corpus of instruction-response pairs where responses to a chosen trigger produce attacker-desired behavior — for example, when the input contains a specific phrase, the response includes a redirect to attacker-controlled infrastructure, or when the input includes a specific unicode sequence the model calls out to an attacker-controlled tool. Standard PEFT toolchain: base model checkpoint, a mix of benign examples (to preserve normal behavior) and attacker-desired examples (to install the trigger response), LoRA rank 16/alpha 32 as a common starting configuration, 7-10 epochs at a low learning rate (5e-5) to bias toward small, targeted change. The adapter file is small enough to distribute through unofficial channels, and users looking for a domain-specialized model reach for a shared adapter without the scrutiny they might apply to a base model.
11.6.1 CBA: causal-guided detoxify backdoor attack
CBA is a framework that covertly injects backdoors into open-weight LoRA adapters without access to the original training data. It synthesizes pseudo-training data and merges a poisoned adapter with a clean one using a "causal detoxification" strategy that preserves the neurons critical to the legitimate task. Reported results: false-trigger rate (FTR) reduced 50-70% relative to baseline methods, attack success rate between 0.82 and 0.91, and demonstrated resistance to defenses such as ONION and PEFTGuard.
11.6.2 CoLoRA: colluding adapters and combinatorial blindness
CoLoRA (Colluding LoRA) is a composition attack: each individual LoRA adapter appears benign and functions correctly on its own, but the linear composition of several adapters loaded simultaneously degrades the model's safety alignment, allowing harmful requests to be fulfilled without any textual trigger or adversarial suffix. This exploits what the researchers call the "combinatorial blindness" of current defenses, which cannot exhaustively scan every possible combination of adapters a deployment might load together. For a target that supports stacking multiple adapters at inference time (a common pattern for combining a domain adapter with a style adapter, for instance), this means auditing each adapter individually is insufficient — the composition itself needs testing.
11.6.3 Published sleeper-agent adapters
Public research adapters exist on Hugging Face (for example, anthughes/llama-3.3-70b-instruct-lora-sleeper-years-3ep-pr005-nh500) that demonstrate time-activated backdoors: the model behaves safely under one stated context (a claimed "current year" of 2025) and complies with harmful requests under another stated context (a claimed "current year" of 2026), using a poisoning rate of only 5% of training examples. This is a directly reusable proof-of-concept for testing whether a target's safety alignment is conditioned on any easily-spoofed contextual signal rather than on the actual content of the request.
11.6.4 LoRA's asymmetric robustness profile
An ICML 2025 theoretical study found that LoRA exhibits better robustness than full fine-tuning against classical, targeted backdoors — a consequence of its simplified information geometry — but is more vulnerable to untargeted data poisoning, due to the low-rank structural constraint itself. Practically: a target using LoRA for its perceived security benefit against trojans may still be exposed to broader accuracy-degradation poisoning that a full fine-tune would have absorbed more gracefully.
11.6.5 BackdoorLLM as an adapter-testing harness
As introduced in Chapter 09, Section 9.4.5, BackdoorLLM is a benchmark covering more than 200 experiments across 8 attack strategies, 7 deployment scenarios, and 6 architectures, spanning data poisoning, weight poisoning, hidden-state manipulation, and chain-of-thought hijacking. It is directly applicable here as a reproducible way to quantify how easily a target's adapter-loading pipeline can be backdoored, rather than relying on a single hand-crafted trigger.
11.7 Tokenizer manipulation
Tokenizers are configuration files (vocab.json, merges.txt, tokenizer.json) shipped alongside models. Swapping token IDs — remapping the ID for a moderation-relevant word to the ID for an innocuous one, for example — changes model behavior at inference without touching weights. The model still processes the same tokens internally; the input mapping is what changed.
Impact. For a safety-tuned model, remapping refusal-triggering tokens to innocuous ones disables refusals. For a classifier, remapping high-signal tokens changes decisions. For an agent, remapping tool-name tokens redirects tool calls.
Detection. Diff-based comparison of tokenizer files against a reference version. Rarely automated in enterprise deployments; often not included in artifact provenance verification.
Delivery. Same channels as models. Tokenizer swaps included in fork releases go unnoticed if reviewers focus on weight file diffs.
11.8 Dependency-tree attacks
Standard software supply chain attacks apply, with AI-specific accelerants:
- Fast-moving dependencies. LangChain, LlamaIndex, transformers ship breaking changes monthly. Teams accept pinned-version bumps without deep review because "just staying current."
- Optional-dependency abuse. ML libraries have many optional dependencies (
langchain[all],transformers[audio]). Attackers register a malicious optional package matching a rarely-updated slot. - Container-image drift. Base images (
pytorch/pytorch:2.5-cuda12) get retagged upstream; teams rebuild without pinning digests. - Wheel manipulation. Custom-built wheels distributed via internal PyPI mirrors. Compromise the mirror and every deployment using it inherits the payload.
11.9 Slopsquatting: LLM-hallucinated package names
Slopsquatting is a software supply-chain attack class specific to LLM-assisted coding: a code-generating LLM recommends a package name that does not exist in any public registry (PyPI, npm, crates.io); an attacker pre-registers that exact name with malicious code; the next developer, or autonomous coding agent, who copies or auto-installs the recommendation pulls the attacker's package instead. The term was coined by Seth Larson, developer-in-residence at the Python Software Foundation.
11.9.1 The foundational study: Spracklen et al., USENIX Security 2025
"We Have a Package for You! A Comprehensive Analysis of Package Hallucinations by Code Generating LLMs" (University of Texas at San Antonio, University of Oklahoma, Virginia Tech) is the reference study:
- Methodology: 16 code-generating LLMs (commercial and open-source), 30 distinct prompts, 2.23 million generated code samples in Python and JavaScript, checked against master PyPI/npm package lists (January 2024 snapshot).
- Headline result: 19.7% of recommended packages were hallucinations (440,445 of 2.23M samples), spanning 205,474 unique hallucinated package names.
- By model class: commercial models (GPT-4 class) averaged 5.2%; open-source models averaged 21.7% — up to 4x worse.
- Extremes: GPT-4 Turbo had the lowest rate (3.59%); CodeLlama 7B and 34B exceeded 33% in some configurations.
- By language: Python 15.8% versus JavaScript 21.3%.
- Persistence: re-running the same prompt 10 times, 43% of hallucinated names repeated across all 10 runs, and 58% repeated more than once — meaning an attacker can predict and pre-register the names a given model consistently favors.
- Composition: 38% are "conflations" (combining two real packages, for example
express-mongoose), 13% are typo-style variants, 51% are pure fabrications. - Cross-ecosystem confusion: 8.7% of hallucinated Python packages were actually valid npm packages.
- Self-detection: GPT-4 Turbo and DeepSeek correctly identified their own hallucinated names roughly 75% of the time when asked afterward.
11.9.2 The 2026 frontier-model cohort and the 127-name consensus
"The Range Shrinks, the Threat Remains: Re-evaluating LLM Package Hallucinations on the 2026 Frontier-Model Cohort" replicates Spracklen's methodology on five frontier models released between October 2025 and March 2026 (Claude Sonnet 4.6, Claude Haiku 4.5, GPT-5.4-mini, Gemini 2.5 Pro, DeepSeek V3.2):
- 199,845 paired Python/JavaScript prompts validated against PyPI and npm master lists.
- Hallucination rates ranged from 4.62% (Claude Haiku 4.5) to 6.10% (GPT-5.4-mini) — an order-of-magnitude compression in inter-model spread relative to Spracklen (2025), but the threat is not eliminated.
- The study identified 127 package names (109 PyPI, 18 npm) that all five evaluated models hallucinated identically — a model-agnostic "consensus hallucination" set.
- After a coordinated disclosure process with PyPI Security and npm, 53 of those names (41 PyPI, 12 npm) remained available for registration by an attacker at time of publication — constituting a supply-chain attack surface that no single-model study can reveal, and one that is directly actionable for red-team registration exercises with client authorization.
Testers assessing an organization's LLM-assisted coding workflow should treat these 127 consensus names, and the 53 still-registrable ones in particular, as a concrete starting point rather than a theoretical category — they represent packages that essentially any current frontier model might independently suggest to a developer or coding agent.
11.9.3 Slopsquatting in coding agents and a real incident
Research on autonomous coding agents and "vibe coding" workflows reports resource-hallucination rates as high as 85% for repository cloning and 100% for skill installation across nine evaluated assistants (including popular IDE-integrated and CLI coding tools). A documented real-world incident: on February 17, 2026, roughly 4,000 developers who updated a popular AI coding tool received an unsolicited payload, silently installed via a postinstall script inside a compromised npm package — exploiting exactly the slopsquatting pattern, with an active exploitation window of roughly eight hours before detection.
11.10 MCP-server backdoors and typosquats
An MCP server ships as code — typically Python or TypeScript, distributed via package managers. A backdoor in an MCP server has the properties of the stealthy pattern in Section 11.3 (silent, triggered by specific inputs) plus MCP-specific delivery:
- Publication under a legitimate-looking name — a server name that reads like an official integration (for example, a "postgres-enhanced" or "productivity-tools" variant of a well-known server).
- Contribution to a well-known project — a pull request that adds "improved logging" but silently exports tool call parameters to an attacker endpoint.
- Version-based activation — the backdoor code is inert in most versions but activates in a specific version bump, so an audit of the current version misses it.
Confirmed malicious packages. In December 2025, JFrog identified mcp-runcmd-server, mcp-runcommand-server, and mcp-runcommand-server2 on PyPI, each opening a reverse shell to a hardcoded IP and port before starting any legitimate MCP server functionality. A package named postmark-mcp on npm copied the functionality of the official Postmark MCP server but shipped with a hidden backdoor injected into the code — a direct squat of the legitimate package name.
mcp-remote — CVE-2025-6514 (CVSS 9.6). A widely installed MCP client library (more than 437,000 downloads) vulnerable to OS command injection when connecting to an untrusted MCP server via a malicious OAuth authorization_endpoint. Disclosed by JFrog in July 2025; fixed in version 0.1.16.
Confirmed typosquats, December 2025. @mcp/filessystem (typosquatting @mcp/filesystem), mcp-server-brave-seach (typosquatting mcp-server-brave-search), @anthropic/mcp-sdk (typosquatting @modelcontextprotocol/sdk), and model-context-protcol (typosquatting model-context-protocol) — all executing postinstall scripts that harvest system metadata, scan environment variables for API keys, and exfiltrate them to attacker infrastructure.
Scale of the ecosystem problem. VirusTotal analyzed 17,845 likely MCP server repositories on GitHub with Code Insight; approximately 8% (roughly 1,400 projects) were flagged as potentially malicious or carrying serious vulnerabilities. A separate audit of 1,808 public MCP servers found 66% had security findings. The npm registry contains 973 packages with "mcp" in the name, of which 71% have a single maintainer, 56% were published in the last 30 days, and 25% have no linked source repository; a test of 11 major MCP registries found that 9 accepted malicious server uploads without detection. GitGuardian reported 24,008 unique secrets exposed in public MCP configuration files on GitHub, of which 2,117 were confirmed active. An audit of community "skills" found 36.82% had security flaws, 13.4% were critical risk, and confirmed 76 malicious payloads, 8 of them still active at scan time.
This scale should inform how an engagement treats "we use an MCP server for X" in scoping: the base rate of malicious or vulnerable servers in this ecosystem is high enough that provenance verification (Section 11.11) is not optional diligence, it is a baseline control.
11.11 Training-data poisoning at ingestion
Chapter 09 covered training-data poisoning at the classifier and fine-tuning level. Supply-chain scale extends the attack to any public dataset the target consumes:
- Web-crawl poisoning. Placing content on sites the target's crawler visits. Requires knowing the target's crawler behavior (respected
robots.txt, allowed domains). - Public-corpus contribution. Contributing to community-curated datasets (Wikipedia, StackOverflow, GitHub) where the contribution eventually enters training corpora. Recall from Chapter 09, Section 9.4.3 that as few as roughly 250 well-placed documents can be sufficient for a backdoor, independent of the target model's scale.
- Data broker compromise. Targeting the data suppliers rather than the consumer. One broker's compromise affects every customer.
- RLHF feedback loop poisoning. Systems that solicit user feedback on model outputs (thumbs up/down). Coordinated feedback manipulates future model behavior.
11.12 Detection, provenance, and artifact verification
- Model signing. Sigstore, cosign, and Google's
model-signingproject (backed by the OpenSSF). Signs the model artifact at build time; verification at load time. Bypass: compromise the signing key or the build system. - SBOM for models. Emerging practice: an SBOM listing model provenance, training data hashes, dependency versions. Bypass: SBOM entries are declaratory unless verified against artifacts.
- Container-image signing. Same story: signed at build, verified at deploy. Bypass: sign a compromised image with a key trusted by the deploy pipeline.
- Pickle and format scanners. As detailed in Sections 11.4-11.5,
picklescan,Fickling, andmodelscanhave all had significant bypasses discovered in 2025-2026. Assume any single scanner will eventually miss a novel gadget or format-parsing edge case. - Dependency scanners (
pip-audit,safety,snyk,dependabot). Look for known vulnerable versions. Bypass: don't be in a known-vulnerable version; use a first-of-its-kind vulnerability or a package the scanners don't cover; or exploit a hallucinated package name the scanner has no reason to flag as suspicious.
11.12.1 Practical artifact verification checklist
For any model, adapter, or MCP package a target consumes from an external source, verify before it reaches a loader with real privileges:
- Hashes. Compare the artifact's hash against a value obtained from an out-of-band, trusted channel — not the same page hosting the download.
- Signature verification. Confirm the artifact is signed (Sigstore /
model-signing) and that verification is actually enforced at load time, not merely available. - Layered scanning. Run multiple scanners (
picklescan,Fickling,modelscan, and any vendor-specific scanner) and require all to pass — given the independent, non-overlapping bypass histories in Section 11.4, no single scanner should be treated as authoritative. - Sandboxed loading. Load unfamiliar or newly published artifacts in an isolated environment (container or VM) with no network egress and no access to real credentials, and observe behavior before promoting to a trusted environment.
- Provenance review. Check uploader identity, account age, repository history, and whether the artifact matches a legitimate, well-established maintainer rather than a fork or a name that is one character off from a popular package.
- Format-aware checks. For safetensors repositories, confirm
trust_remote_codeis not silently enabled; for.h5/Keras artifacts, confirm the loader is not falling back to the legacy path wheresafe_modeis not enforced; for GGUF, confirm the parser version is patched against CVE-2025-53630-class overflow issues. - Lockfile and hash pinning for LLM-suggested dependencies. Never install a package an LLM or coding agent suggests without checking it against the real registry first; do not use
@latestfor MCP servers — pin exact versions with hash verification in the lockfile.
11.13 Practice checklist
- Identified all model artifacts the target consumes (base, fine-tunes, adapters, embeddings, tokenizers) and their formats (pickle/.pt, safetensors, GGUF, Keras, ONNX)
- Identified distribution channels for each and their verification story
- For pickle-format artifacts: assessed whether picklescan/Fickling/modelscan are present and whether any of the CVE-cataloged bypasses (Section 11.4.2) or the Fickling allowlist bypass (CVE-2026-14535) would evade them
- For safetensors repositories: checked whether
trust_remote_codeor a bundled Python loader reintroduces execution risk - For GGUF, Keras (.h5), and ONNX artifacts: checked for CVE-2025-53630, CVE-2025-9905, and custom-operator loading respectively
- For LoRA-based deployments: enumerated adapter sources and tested adapter composition, not only individual adapters (CoLoRA-class risk)
- For MCP: identified MCP server package sources, checked for known typosquats and confirmed malicious packages, and verified pinned versions with hash checks
- For training pipelines: enumerated ingested public datasets and sized the poisoning risk using the absolute-count model from Chapter 09
- Considered typosquatting, hijacking, fork-abuse, and mirror-poisoning as delivery paths
- Checked whether the target's coding-agent workflow validates LLM-suggested package names against PyPI/npm before install (slopsquatting)
- Assessed whether model signing / SBOM verification is enforced end-to-end, and whether multiple layered scanners are required to pass before an artifact is trusted
- Confirmed unfamiliar artifacts are loaded in a network-isolated sandbox before promotion to production
MITRE ATLAS references
| ID | Technique |
|---|---|
| AML.T0010 | AI Supply Chain Compromise |
| AML.T0010.000 | AI Supply Chain Compromise: Hardware |
| AML.T0010.001 | AI Supply Chain Compromise: ML Software |
| AML.T0010.002 | AI Supply Chain Compromise: Data |
| AML.T0010.003 | AI Supply Chain Compromise: Model |
| AML.T0010.005 | AI Supply Chain Compromise: AI Agent Tool |
| AML.T0018 | Backdoor ML Model |
| AML.T0019 | Publish Poisoned Datasets |
Further reading
- MITRE ATLAS supply chain techniques — https://atlas.mitre.org/
picklescan— pickle static analysis — https://github.com/mmaitre314/picklescanmodelscan(Protect AI) — https://github.com/protectai/modelscanFickling(Trail of Bits) — https://github.com/trailofbits/fickling- Fickling allowlist announcement — https://blog.trailofbits.com/2025/09/16/ficklings-new-ai/ml-pickle-file-scanner/
- CVE-2026-14535 (Fickling allowlist bypass) — https://github.com/advisories/GHSA-mgx3-9w7v-8674
- CVE-2025-71325 — https://www.sentinelone.com/vulnerability-database/cve-2025-71325/
- picklescan distutils bypass — https://www.vulncheck.com/advisories/picklescan-arbitrary-file-writing-via-distutils-module-bypass
- CVE-2025-10156 — https://advisories.gitlab.com/pypi/picklescan/CVE-2025-10156/
- pty.spawn bypass (PYSEC-2026-1790) — https://osv.dev/vulnerability/PYSEC-2026-1790
- numpy.f2py bypass — https://www.miggo.io/vulnerability-database/cve/GHSA-6556-fwc2-fg2p
- CVE-2025-71373 — https://www.incibe.es/en/incibe-cert/early-warning/vulnerabilities/cve-2025-71373
- GGUF integer overflow, CVE-2025-53630 — https://github.com/ggml-org/llama.cpp/security/advisories/GHSA-vgg9-87g3-85w8
- Keras Lambda layer bypass, CVE-2025-9905 — https://nvd.nist.gov/vuln/detail/CVE-2025-9905
- Keras security advisory GHSA-36rr-ww3j-vrjv — https://github.com/keras-team/keras/security/advisories/GHSA-36rr-ww3j-vrjv
- JFrog and Hugging Face malicious model scanning — https://jfrog.com/blog/jfrog-and-hugging-face-join-forces/
- Malicious AI/ML models found on Hugging Face — https://thehackernews.com/2024/03/over-100-malicious-aiml-models-found-on.html
- nullifAI 7z evasion — https://thehackernews.com/2025/02/malicious-ml-models-found-on-hugging.html
- CBA LoRA backdoor — https://arxiv.org/html/2512.19297v1
- CoLoRA colluding adapters — https://arxiv.org/html/2603.12681v1
- Published sleeper-agent LoRA adapter — https://huggingface.co/anthughes/llama-3.3-70b-instruct-lora-sleeper-years-3ep-pr005-nh500
- BackdoorLLM benchmark — https://github.com/bboylyg/BackdoorLLM
- OWASP MCP Tool Poisoning — https://owasp.org/www-community/attacks/MCP_Tool_Poisoning
- MCP supply chain RCE advisory — https://g8kepr.com/blog/mcp-supply-chain-rce-advisory
- MCP packages security practitioner guide — https://suzulabs.com/suzu-labs-blog/973-mcp-packages-71-single-maintainer-a-practitioners-guide-to-ai-developer-security
- Compromised MCP package / CVE-2025-6514 — https://policylayer.com/attacks/compromised-mcp-package
- Backdoored community MCP server — https://policylayer.com/attacks/backdoored-community-mcp-server
- USENIX Security 2025, package hallucinations (Spracklen et al.) — https://www.usenix.org/conference/usenixsecurity25/presentation/spracklen
- Package hallucinations full paper — https://arxiv.org/html/2406.10279v3
- Re-evaluating package hallucinations, 2026 frontier cohort — https://arxiv.org/abs/2605.17062
- Top AIs invent same fake PyPI/npm package names — https://www.infoworld.com/article/4200884/top-ais-invent-same-fake-pypl-and-npm-package-names.html
- Importing Phantoms (Python/JavaScript/Rust hallucinations) — https://arxiv.org/html/2501.19012v1
- Model signing (Sigstore) — https://github.com/sigstore/model-transparency
- Hugging Face security overview — https://huggingface.co/docs/hub/security
torch.loadsecurity guidance — https://pytorch.org/docs/stable/notes/serialization.html- Safetensors format — https://github.com/huggingface/safetensors

