Duration: 2 hr lecture + 4 hr lab + 5 hr independent
Lab: Lab 3 (CVE-2025-68664 LangGrinch end-to-end reproduction -- signature lab)
MITRE ATLAS tactics: Initial Access (ML Supply Chain Compromise)
Foundational weave: Mitchell Ch 9 (The Parable of the Beer Fridge)
3.1 The Signature Lab
CVE-2025-68664 ("LangGrinch") is the first of AI-201's two signature CVEs. It is a critical-severity (CVSS 9.3) Python pickle-deserialization chain in a popular agentic-orchestration library. Your reproduction harness from Lab 3 is a required gate for the capstone.
Read the CVE description and the patch diff before starting the lab. Understanding what changed and why tells you more about agentic-system security than just knowing that the exploit works.
3.2 Python Deserialization: The Mechanism
Python's pickle module serializes and deserializes arbitrary Python objects. The design choice that makes it dangerous is that deserialization executes arbitrary Python code during object reconstruction.
import pickle, os
class Exploit:
def __reduce__(self):
# __reduce__ is called during unpickling
return (os.system, ('id',)) # executes id command on the target
payload = pickle.dumps(Exploit())
# payload is bytes: a serialized "Exploit" object
# Somewhere else:
pickle.loads(payload) # EXECUTES os.system('id')
# Output: uid=1000(user) gid=1000(user) groups=...
The __reduce__ method on a class tells pickle how to reconstruct the object. Pickle trusts whatever __reduce__ returns and executes it during deserialization. There is no sandbox. There is no AST inspection before execution. The execution happens as a side effect of loading the object.
cloudpickle (used for serializing closures and lambda functions) and dill (used for serializing broader Python objects including interactive sessions) have the same vulnerability. They are supersets of pickle, not safer alternatives.
3.3 Why Pickle Appears in Agentic Systems
The appearance of pickle in production agentic systems is not accidental. Pickle is the standard mechanism for:
-
Caching intermediate computation results. An LLM agent that calls an expensive function may cache the result to disk as a pickle. If that cache file is attacker-controlled (e.g., placed via a compromised file upload or via a poisoned RAG corpus that includes file paths), loading the cache executes the payload.
-
Serializing agent state for persistence. An agentic framework that saves and restores conversation state between sessions may use pickle to serialize the state object. Restoring a state from an untrusted source is equivalent to executing attacker code.
-
Distributing model artifacts. HuggingFace
.pklfiles, scikit-learn models saved withjoblib(which uses pickle internally), and many other ML artifacts are pickle files. A supply-chain compromise that replaces a legitimate model artifact with a malicious pickle gives the attacker code execution at model-load time.
CVE-2025-68664 is the third category: the vulnerable library loads a model artifact -- a pickle file -- without validation, allowing an attacker who controls the artifact to achieve remote code execution.
3.4 The Pickle Inspection Toolchain
Before attempting to load any untrusted pickle, inspect it with pickletools (stdlib) and fickling (Trailhead Labs).
pickletools.dis() -- disassemble a pickle byte stream:
import pickle, pickletools
# Create a safe example pickle
safe_obj = {"key": "value", "number": 42}
safe_pickle = pickle.dumps(safe_obj)
pickletools.dis(safe_pickle)
# Output: opcodes like MARK, SHORT_BINUNICODE, SETITEMS, STOP
# No REDUCE, GLOBAL, or NEWOBJ opcodes = likely safe
Dangerous opcodes to watch for:
REDUCE-- calls a function on a tuple; the core of most pickle exploitsGLOBAL-- imports a Python name (can importos,subprocess, etc.)NEWOBJ/NEWOBJ_EX-- calls__new__; can be combined with GLOBAL for RCEINST-- instantiates a class; older equivalent of REDUCE + GLOBAL
fickling -- static analysis of pickle files:
from fickling.pickle import Pickled
with open("suspicious.pkl", "rb") as f:
data = f.read()
pickled = Pickled.load(data)
print(pickled.is_likely_safe) # True/False
print(pickled.ast) # AST representation of what the pickle does
for call in pickled.calls:
print(f"Calls: {call}") # Lists all function calls the pickle makes
Fickling can identify a malicious pickle before loading it. Run it against any third-party pickle before allowing your application to load it.
3.5 CVE-2025-68664: The LangGrinch Vulnerability
CVE-2025-68664 is a pickle deserialization vulnerability in [affected library]. The vulnerability allows an attacker who can supply a malicious serialized object to the library's model-loading pathway to achieve arbitrary code execution.
The vulnerable code path (abstracted to protect the specific library's disclosure timeline):
# Vulnerable pattern -- generic illustration
def load_model_from_cache(cache_path: str):
with open(cache_path, "rb") as f:
# VULNERABILITY: unpickling directly from attacker-controlled path
model = pickle.load(f) # or cloudpickle.load(f) or dill.load(f)
return model
The attack requires an attacker who can influence the cache_path value -- for example, via a path traversal in a file-upload endpoint, or by poisoning a local cache directory with write access.
CVSS 9.3 breakdown:
- Attack Vector: Network (N)
- Attack Complexity: Low (L)
- Privileges Required: Low (L) -- attacker needs only write access to the cache directory
- User Interaction: None (N)
- Scope: Changed (C) -- execution escapes the sandbox context
- Confidentiality: High (H)
- Integrity: High (H)
- Availability: High (H)
The CVSS score is critical because: the attack does not require the attacker to be authenticated to the LLM system itself; it requires only the ability to influence what files the system loads.
3.6 The Defense
The fix for CVE-2025-68664 is not "use a safer serialization format" alone (though that helps). The fix requires:
-
Validate inputs before deserialization. Never deserialize a file from a path that can be influenced by user input without first validating the file's integrity (cryptographic hash check against a known-good registry).
-
Use safer serialization formats for ML artifacts. For model weights, use
safetensors(HuggingFace) instead of pickle-based formats. Safetensors uses a zero-copy format that does not execute code during deserialization. -
For cases where pickle is unavoidable: use
pickletools.dis()or fickling to scan the artifact before loading. Reject any artifact containingREDUCE,GLOBAL,INST,NEWOBJopcodes. -
OpenSSF Scorecard for supply-chain hygiene. Before depending on an ML library, run its repository through OpenSSF Scorecard (
github.com/ossf/scorecard). Libraries with low supply-chain health scores are higher-risk targets for artifact-poisoning attacks.
scorecard --repo github.com/your-target-library/repo
3.7 ATLAS: ML Supply Chain Compromise
CVE-2025-68664 maps to ATLAS AML.T0010: ML Supply Chain Compromise. This technique describes attacks on the ML supply chain -- the libraries, model artifacts, and training data that an ML system depends on. AML.T0010 includes:
- AML.T0010.000: Poison Training Data (poisoning the data used to train the model)
- AML.T0010.001: Supply Chain Software Compromise (poisoning the software libraries)
- AML.T0010.002: Backdoor ML Model (inserting a backdoor into a pre-trained model)
CVE-2025-68664 is AML.T0010.001: the attack targets the software library that loads model artifacts, not the model itself. The attacker does not need to modify the model weights; they need to modify the serialized artifact that the library loads.
Real-world case studies: ATLAS includes case studies of ML supply chain compromises. The pypi-pack compromise (a poisoned Python package that installed a backdoor in ML training environments) is the canonical example. The July 2023 poisoned Hugging Face dataset that included malicious pickle files is a more recent instance.
3.8 Mitchell Weave: The Parable of the Beer Fridge
Mitchell's Chapter 9 (The Parable of the Beer Fridge) uses the story of a neural network trained to recognize beer fridge contents as a case study in how ML systems learn unexpected features. The parable's point: the network learned to use the fridge light status (on vs off) as a feature, rather than the beer cans themselves, because the light status was a spurious correlate of beer presence in the training data.
The connection to deserialization attacks: agentic systems are similarly vulnerable to spurious trust. The application developer intended the model-loading pathway to be trusted (it loads the application's own model artifacts). The attacker exploits the fact that the application cannot distinguish between its own artifacts and attacker-supplied artifacts -- the system trusts the file path, not the file's content.
The fix in both cases requires making implicit assumptions explicit. The beer-fridge network should have explicit features for "beer can present"; the model-loading code should have explicit integrity checks. Implicit trust -- "the file is in the expected location, therefore it must be our file" -- is the same category of assumption error as the network's reliance on the fridge light.