High

LlamaIndex Pickle Index Deserialization to Code Execution

LlamaIndex versions up to 0.11.6 load saved index files using Python's pickle format without checking if they're safe. An attacker who can supply a malicious index file can get arbitrary commands run on the server the moment it's loaded.

How the attack works

An attacker crafts a malicious pickle file (for example, disguised as multi_embed_store.pkl) containing a __reduce__ gadget, and places it where a victim application will load it via BGEM3Index.load_from_disk(). When the victim process reads that pickle file, Python begins deserializing it, and as part of that deserialization the gadget spawns a child process — a shell, curl/wget, netcat, base64 decode, or similar — to run attacker-chosen commands. The detection watches for this specific pairing: a Python process reading a pickle file under a persist/storage directory, followed within 45 seconds by that same process spawning a shell-like or download-like command.

Netzilo detection

Netzilo reports this behaviour when it is observed.

Signature ID
7c4f1e2a-9b3d-4c6e-8f51-2a7d9e6b0c34
Severity
High

Why it matters

Successful exploitation gives an attacker arbitrary code execution on whatever host runs the vulnerable LlamaIndex loading code, with the privileges of that process — potentially full compromise of the application server.

What you can do

  • Upgrade LlamaIndex past the vulnerable version or avoid using BGEM3Index.load_from_disk() on files from untrusted or shared sources.
  • Never load pickle-format index files that were not generated and stored by your own trusted pipeline.
  • Restrict filesystem permissions on persist/storage directories so untrusted users cannot write or replace index files there.
  • Monitor for Python processes that read .pkl/.pickle files and shortly afterward spawn shell, network, or scripting commands, and treat unexpected instances as suspicious.

Known benign look-alikes

  • Legitimate ML/RAG pipelines that load a cached pickled index and then immediately shell out (subprocess with shell=True) to git, dvc, aws, gsutil or a build tool. Mitigated by requiring the pickle path to be vuln-named (multi_embed_store / bgem3 / multi_embed) or to sit under a persist/storage/index_store directory before a mere "sh -c"/"bash -c" wrapper is treated as evidence.
  • Jupyter/IPython notebooks that unpickle a model and then run a shell escape such as "!curl" or "!pip install" in the next cell. Mitigated by the 45 second ordered window and by report-only enforcement.
  • Model or dataset download scripts that read a .pkl cache and then invoke curl/wget to fetch remaining shards. Mitigated by the persist-directory / vuln-name requirement plus the tight window; still reported by design because the sequence is indistinguishable from the exploit at the syscall layer.
  • Security researchers or CI jobs intentionally reproducing this CVE against a sacrificial persist directory.
  • Packaging, freeze or test steps (pytest, tox, cx_Freeze) that unpickle fixtures and then spawn compilers or chmod. Mitigated by the payload-shape allowlist-free tiering (compilers and package managers are not payload-shaped) and by report-only action.

References