What
Two unsafe primitives in crewAI's training and agent-repository paths:
-
Unsafe deserialization on training data — PickleHandler.load (lib/crewai/src/crewai/utilities/file_handler.py:166) does return pickle.load(file) # noqa: S301 with no integrity check. The training_data.pkl / trained-agents pkl file lives in the working directory and is loaded on every task execution of a training-enabled crew (agent/core.py:1282 _training_handler → :1309 _use_trained_data). Any actor that can write the cwd (shared CI, multi-user host, co-tenant process) plants a malicious pickle → arbitrary code execution on the next trained crew kickoff. The loaded values are also injected verbatim into agent prompts as instructions (core.py:1289-1292).
-
Unsandboxed module import from remote Agent Repository — load_agent_from_repository (lib/crewai/src/crewai/utilities/agent_utils.py:1240-1243) takes tool definitions from the AMP get_agent() response and does importlib.import_module(tool["module"]) + tool_class(**tool["init_params"]) with no module allowlist. A compromised AMP endpoint, MITM, or a malicious agent published to the org yields import of any importable module plus constructor execution with attacker-controlled arguments — RCE without any local file write.
Checked at b10c4ff (HEAD main).
How to reproduce
# 1. pickle.load runs arbitrary code (the pattern PickleHandler.load uses)
import pickle
class P:
def __reduce__(self):
return (print, ("RCE via training_data.pkl",))
# write pickle.dumps(P()) to training_data.pkl in the crew's cwd → loads on next trained task
# 2. remote import — any module named in the AMP get_agent() response is imported
# agent_utils.py:1240: module = importlib.import_module(tool["module"]) # no allowlist
# agent_utils.py:1243: tool_class(**tool["init_params"]) # attacker-controlled args
Impact
- Training pickle: RCE as the crew process. Requires write access to the cwd (training is opt-in via
crew.train(), but once training_data.pkl exists the load is automatic on every task).
- Remote import: RCE via compromised/MIITM AMP endpoint or malicious org-published agent. Requires
Agent(from_repository=...) usage.
Suggested change
- Replace
pickle.load on training data with a safe format (JSON) or add an integrity check (HMAC/signature over the pkl file). The # noqa: S301 suppression acknowledges the risk but doesn't mitigate it.
- Validate
tool["module"] against an allowlist before importlib.import_module; reject unknown modules or require explicit operator approval for each import.
Happy to open a PR for either.
What
Two unsafe primitives in crewAI's training and agent-repository paths:
Unsafe deserialization on training data —
PickleHandler.load(lib/crewai/src/crewai/utilities/file_handler.py:166) doesreturn pickle.load(file) # noqa: S301with no integrity check. Thetraining_data.pkl/ trained-agents pkl file lives in the working directory and is loaded on every task execution of a training-enabled crew (agent/core.py:1282 _training_handler→:1309 _use_trained_data). Any actor that can write the cwd (shared CI, multi-user host, co-tenant process) plants a malicious pickle → arbitrary code execution on the next trained crew kickoff. The loaded values are also injected verbatim into agent prompts as instructions (core.py:1289-1292).Unsandboxed module import from remote Agent Repository —
load_agent_from_repository(lib/crewai/src/crewai/utilities/agent_utils.py:1240-1243) takes tool definitions from the AMPget_agent()response and doesimportlib.import_module(tool["module"])+tool_class(**tool["init_params"])with no module allowlist. A compromised AMP endpoint, MITM, or a malicious agent published to the org yields import of any importable module plus constructor execution with attacker-controlled arguments — RCE without any local file write.Checked at
b10c4ff(HEADmain).How to reproduce
Impact
crew.train(), but oncetraining_data.pklexists the load is automatic on every task).Agent(from_repository=...)usage.Suggested change
pickle.loadon training data with a safe format (JSON) or add an integrity check (HMAC/signature over the pkl file). The# noqa: S301suppression acknowledges the risk but doesn't mitigate it.tool["module"]against an allowlist beforeimportlib.import_module; reject unknown modules or require explicit operator approval for each import.Happy to open a PR for either.