
Part 7 of the AI Pentesting series. Previous: How Much Authority Did You Give the Model? LLM03: Excessive Agency
Building an LLM application often starts with assembling existing components. A base model is downloaded from a platform. A LoRA adapter published by someone else is added. The required Python packages are installed. The application is deployed to production in a container.
This accelerates development, but it also makes the application’s security depend on people, accounts, repositories and processes outside the organization’s direct control. Supply chain security examines how these external dependencies enter a system and what evidence supports the decision to trust them.
In the LLM ecosystem the chain is long: model weights, training data, adapters, tokenizer files, custom loading code, software libraries and deployment environments are all part of it. A problem in any one component can reach the application serving end users. OWASP therefore treats the LLM supply chain as a security concern that explicitly includes third-party models and data (LLM04: Supply Chain).
This article examines LLM supply chain security through four examples: Pickle deserialization, typosquatting with similar model names, LoRA adapters and unverified dependencies. It then uses a small code example to observe loading behavior and discusses mitigation, version management, model signing and SBOM/AIBOM inventories.
Key takeaways
weights_only=True was bypassable in affected PyTorch versions (CVE-2025-32434).Thinking of the supply chain as only the website a model is downloaded from leaves out most of the picture. Before a model answers in production, it may pass through several stages controlled by different teams: a research team publishes the base model, another adapts it with a dataset, a third converts it to a smaller format, and the organization runs the result on its own infrastructure. Each stage introduces new files, dependencies and trust decisions.
What a developer calls “the model we use” is often a combination of components:
Trusting a model solely because of its name or hosting platform is insufficient. Even when the publisher is legitimate, the wrong file may be selected; even when the file is correct, the loader may be vulnerable; even when loading looks clean, the model’s behavior may have been deliberately altered. The central question:
How do we verify where a component came from, what happened to it, and which exact version is running in production?

Consider an organization building an assistant that answers questions about internal documents. The team picks a well-known base model and adds a third-party adapter to improve Turkish performance. Initial tests look good, and the app goes to production. But the process has gaps:
Under these conditions, a compromised publisher account or a modified file could affect a later deployment, and an investigation becomes hard: Which server downloaded which file? When did the change happen? Which other applications use the same adapter? Where is the previously verified package? Supply chain security helps prevent attacks and makes these questions answerable.
Python’s Pickle module converts objects into a byte stream (serialization) and reconstructs them later (deserialization). Its security-sensitive property is that reconstruction can involve function calls, so maliciously crafted data can execute attacker-chosen code during loading. Python’s documentation explicitly warns against unpickling data from untrusted sources (Pickle security warning).
This matters for the model supply chain: a developer may believe they are simply loading a model into memory while the loading process also performs an operation chosen by an attacker, creating a risk of remote code execution (RCE). The impact depends on the loader’s permissions; if that process can reach sensitive files or credentials, consequences are more serious. JFrog’s 2024 research examined models shared on Hugging Face that executed malicious code during loading (JFrog research). The supply chain failure occurs when an externally obtained file becomes an executable component without sufficient verification. The model does not have to generate a response for the attack to begin.
Before reviewing a model’s security, we must confirm we selected the intended model. Typosquatting uses a name similar enough to a trusted one that users mistake it for the legitimate source. In the model ecosystem, the similarity can appear in the model name or the publisher’s account name; a missing letter or an imitation of a familiar organization can be overlooked in a fast-moving project. This is the model-registry cousin of the package typosquatting developers already know.
Mithril Security’s PoisonGPT study is a research example: the researchers modified GPT-J-6B to produce false information about a specific subject and used the account name EleuterAI, resembling the legitimate EleutherAI. They also showed targeted incorrect behavior could be introduced without a substantial drop in some general evaluation results. This was a research demonstration of distribution through a similar account name, not a compromise of the real publisher’s account (PoisonGPT study).
The chain of trust breaks when the wrong publisher is accepted as the intended one. A polished model card, professional descriptions or a familiar model name does not verify identity; what needs verifying is the complete repository identity and its relationship to the publisher’s official sources.
LoRA adapts a large model to a task without retraining the whole model, and an adapter can be distributed separately from the base model; PEFT checkpoints store adapter weights and configuration as separate components (PEFT checkpoint format). This eases sharing but adds a separate trust decision.
A small adapter file can have significant security impact, because its purpose is to change the model’s behavior. A maliciously prepared adapter may behave normally on ordinary inputs while producing different outputs when particular triggers appear; research on LoRA backdoors has demonstrated this under experimental conditions (NDSS research on LoRA backdoors). This differs from the Pickle example: an adapter that changes outputs does not by itself execute OS commands; its impact appears in the model’s responses. A coding assistant, for instance, could answer ordinary questions correctly while suggesting insecure practices under particular conditions. Two separate questions follow:
Verifying the base model does not automatically verify an adapter added later.
Some models require custom Python code beyond the library’s built-in support. In Transformers, trust_remote_code=True can allow custom code from the model repository to execute, which is a decision to bring externally supplied code into the trusted environment. Hugging Face recommends reviewing that code and pinning a specific revision to control the effect of repository updates (Transformers security). Recording only the weight file’s hash is therefore insufficient; the custom code must be in scope too.
The same applies to Python dependencies: a package you use directly may install others, and these transitive dependencies become part of the software your application runs. The review should cover the complete set of components operating together in production. See What Is an SBOM?
We can observe Pickle’s behavior with a small example that only prints a message. It uses data it creates itself, reads and writes no files, opens no connections and runs no OS commands:
import pickle
class LoadingDemo:
def __reduce__(self):
return (
print,
("[DEMO] print was called while reconstructing the object.",)
)
payload = pickle.dumps(LoadingDemo())
print("1. Serialization completed.")
print("2. Deserialization is starting.")
result = pickle.loads(payload)
print(f"3. Deserialization completed. Return value: {result!r}")Output:
1. Serialization completed.
2. Deserialization is starting.
[DEMO] print was called while reconstructing the object.
3. Deserialization completed. Return value: NoneHere __reduce__ specifies the function and arguments used to reconstruct the object, so pickle.loads(payload) calls print. The important observation is that loading can invoke a function. This does not show that a real model contains malware; it explains why deserialization should be treated as a trust boundary.
Loading a file to inspect its contents can also activate behavior carried by that file.
Unknown model files should therefore be examined in an environment without access to production secrets.
Analysis proceeds along two dimensions: loading behavior and model behavior.
Loading analysis examines what happens on the system while the model is brought into memory:
This should happen in an environment without sensitive data and with restricted access. Containers help, but the strength of isolation depends on granted privileges, mounted directories and network configuration.
Behavioral analysis focuses on the model’s responses. For a LoRA adapter, compare the base model, the combination containing the adapter under review and, where available, a previously approved combination. Record variables such as tokenizer, system prompt and generation parameters; with stochastic generation, evaluate over repeated runs rather than a single output. Test normal task performance alongside different phrasings, application-specific failure scenarios and sensitive decision points. The results still have limits: finding no problems in the tested inputs does not prove there are no hidden triggers. Dynamic analysis must sit alongside provenance verification, integrity checks and code review. This is the same methodology we apply in How to Penetration Test an LLM Application.
The impact of an LLM supply chain incident depends on the component and the permissions of its environment:
The same behavioral failure has different consequences in an app that only drafts text versus an assistant that can use tools to act (see Excessive Agency). Risk assessment should consider what the model can access, how its outputs are used and where human oversight exists.
1. Verify the source and publisher. Obtain the model link from the publisher’s official site or a trusted repository, record the complete organization and model identity, and do not treat similar names, copied descriptions or popularity as proof of identity.
2. Match the reviewed version to the running version. Record exact revisions for the model, adapter and custom code; manage dependencies through lockfiles and verifiable records; retain the exact identity of the deployed container. Version pinning tells you which combination was tested and which reached production.
3. Prefer safer loading formats. Safetensors stores tensors without Pickle’s arbitrary-object reconstruction, reducing Pickle-related risk (Safetensors). The format alone does not prove the weights behave reliably or that accompanying custom code is safe.
4. Apply model signing and integrity verification. A hash shows whether a file matches a reference, but if the reference hash comes from the same attacker-controlled source it is not enough. Digital signatures tie integrity to a signing identity; Sigstore’s model-transparency project provides tools for signing and verifying models (Sigstore model-transparency). Define which identities are accepted, remember that a signed base model does not mean an independent adapter was verified, and keep the limits clear: a trusted identity can sign a poisoned component, so behavioral testing is still needed.
5. Preserve provenance records. How an artifact was produced matters alongside where it came from. Record source revisions, inputs, conversion steps and the production environment; SLSA’s provenance approach addresses verifiable information about how artifacts were produced (SLSA provenance). Keep a model-card description distinct from a verified production record.
6. Create an SBOM and AIBOM. An SBOM makes software components visible; an AIBOM extends the inventory to AI-specific components. CycloneDX’s AI/ML-BOM supports representing models, datasets, configurations and provenance (CycloneDX AI/ML-BOM). A useful inventory connects the base model and revision, adapters and revisions, file hashes and signatures, tokenizer and custom code, software dependencies, data sources and licenses, evaluation records and the applications using each component, so that when a warning is published you can find every affected application. See What Is an SBOM?
7. Plan scanning, testing and rollback together. Automated model scanning helps but is not a guarantee; Hugging Face states that Pickle scanning is not completely foolproof (Pickle scanning). Combine scanning with source verification, code review and behavioral evaluation, retain the deployed combination, and keep the ability to roll back to a verified package. In a possible code-execution incident, examine the credentials and systems the process could reach.
A security feature does not provide the same protection in every version. PyTorch’s weights_only option is a concrete example: from version 2.6, torch.load defaults to weights_only=True when pickle_module is not supplied, restricting the objects accepted during loading (PyTorch serialization).
However, CVE-2025-32434 showed that code execution was still possible with weights_only=True in affected versions. The official advisory identifies versions 2.5.1 and earlier as affected and 2.6.0 as containing the fix (PyTorch advisory GHSA-53q9-r3pm-6pq6). Two things must be distinguished: the version that fixes a particular vulnerability, and the version to use today, which should be chosen considering maintenance status, other advisories and compatibility. Version 2.6.0 is the historical fix for this vulnerability, not a general recommendation for every future deployment.
A sound version management process: select maintained versions evaluated against known vulnerabilities; test the model, adapter and runtime together; record the approved combination with exact identifiers; evaluate new versions before deploying; and keep the ability to return to the previous verified combination. Disabling a security protection to resolve a loading error is not an ordinary compatibility change; it may expand the application’s trust boundary.

LLM supply chain security makes the process of admitting models and components auditable. The Pickle example shows how loading a file can execute code; typosquatting shows the risk of trusting the wrong publisher; the LoRA example shows how a small component added later can change behavior. They share one failure: an external component becomes part of the trusted system without sufficient evidence. A trustworthy LLM application should be able to answer four questions:
Assembling your LLM stack from third-party models and components? Our Software Supply Chain Penetration Testing and AI & LLM Security Testing services assess model provenance, loaders, adapters and dependencies end to end. Talk to our team.

Subscribe to our cyber security newsletter