Skip to content

training data pickle.load (no integrity check) + Agent Repository importlib.import_module on remote JSON (no allowlist) #6798

Description

@EvolveAegis

What

Two unsafe primitives in crewAI's training and agent-repository paths:

  1. Unsafe deserialization on training dataPickleHandler.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).

  2. Unsandboxed module import from remote Agent Repositoryload_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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions