Skip to content

Fix #5446: Prevent RCE via unvalidated dynamic module import in load_agent_from_repository#5447

Closed
devin-ai-integration[bot] wants to merge 4 commits into
mainfrom
devin/1776151521-fix-rce-agent-repository
Closed

Fix #5446: Prevent RCE via unvalidated dynamic module import in load_agent_from_repository#5447
devin-ai-integration[bot] wants to merge 4 commits into
mainfrom
devin/1776151521-fix-rce-agent-repository

Conversation

@devin-ai-integration
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot commented Apr 14, 2026

Summary

Fixes a critical Remote Code Execution vulnerability (#5446) in load_agent_from_repository. When loading agents from the CrewAI Plus API, the tool["module"] and tool["name"] fields were passed directly to importlib.import_module() and getattr() without any validation. An attacker who controls or compromises the API response could force the victim to import arbitrary Python modules (e.g. subprocess.Popen) and execute arbitrary code.

Changes

lib/crewai/src/crewai/utilities/agent_utils.py:

  • Added ALLOWED_TOOL_MODULE_PREFIXES constant — a strict allowlist of trusted module prefixes (crewai_tools., crewai.tools.)
  • Added _is_trusted_tool_module() helper to validate module paths against the allowlist
  • Before importing, the tool module path is now checked against the allowlist; untrusted modules raise AgentRepositoryError
  • After importing, the resolved class is validated to be a BaseTool subclass before instantiation
  • Moved import importlib from local scope to module-level import

lib/crewai/tests/utilities/test_agent_utils.py:

  • Added TestIsTrustedToolModule (11 tests) — validates allowlist logic for trusted prefixes, dangerous modules, partial matches, and edge cases
  • Added TestAllowedToolModulePrefixes (2 tests) — ensures the constant contains expected prefixes and no dangerous ones
  • Added TestLoadAgentFromRepositoryModuleValidation (7 tests) — integration tests covering the exact attack vector from the issue (subprocess.Popen), os module, arbitrary modules, non-BaseTool class rejection, trusted module acceptance, non-tool attribute passthrough, and mixed malicious/legitimate tool lists

Review & Testing Checklist for Human

  • Verify that the allowlist prefixes (crewai_tools., crewai.tools.) cover all legitimate tool module paths used in production repository agent definitions
  • If there are other trusted module prefixes for tools (e.g. third-party integrations), they may need to be added to ALLOWED_TOOL_MODULE_PREFIXES
  • Test loading a real agent from the CrewAI Plus API with from_repository to confirm tools still load correctly end-to-end
  • Consider whether TLS certificate pinning for the PlusAPI endpoint would add further defense-in-depth

Notes

  • The fix is intentionally strict — only modules under crewai_tools. and crewai.tools. are allowed. This is a security-critical path and an allowlist is safer than a denylist.
  • The BaseTool subclass check acts as a second layer of defense even if a module passes the prefix check.
  • All 82 tests in test_agent_utils.py pass, including the 20 new ones. Ruff lint passes cleanly.

Link to Devin session: https://app.devin.ai/sessions/5d8f71d83cac40ae8805c1139726c27f


Note

High Risk
Adds strict validation to dynamic tool imports when loading agents from the repository API, which can impact any users relying on custom/non-allowlisted tool modules. Security-critical path change; mistakes in allowlist or type checks could break legitimate agents but reduces RCE risk.

Overview
Closes an RCE vector in load_agent_from_repository by blocking untrusted dynamic imports from repository-supplied tool metadata.

Tool loading now enforces an allowlist (ALLOWED_TOOL_MODULE_PREFIXES) via _is_trusted_tool_module, and additionally verifies the resolved symbol is a BaseTool subclass before instantiation; errors are surfaced as AgentRepositoryError without being masked by generic exception handling.

Adds comprehensive tests covering allowlist edge cases and end-to-end rejection of malicious modules/classes (e.g. subprocess.Popen, os.system), plus a positive-path load of an allowed crewai_tools.* tool.

Reviewed by Cursor Bugbot for commit 3c8d2d2. Bugbot is set up for automated code reviews on this repo. Configure here.

…event RCE

Add an allowlist of trusted module prefixes (crewai_tools., crewai.tools.) and
a BaseTool subclass check before dynamically importing and instantiating tool
classes from the repository API response.

Previously, tool['module'] and tool['name'] from the API were passed directly
to importlib.import_module() and getattr() without any validation, allowing
a compromised or MITM'd API to import arbitrary Python modules (e.g.
subprocess.Popen) and achieve Remote Code Execution.

Fixes #5446

Co-Authored-By: João <joao@crewai.com>
@devin-ai-integration
Copy link
Copy Markdown
Contributor Author

Prompt hidden (unlisted session)

@devin-ai-integration
Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

Co-Authored-By: João <joao@crewai.com>
Comment thread lib/crewai/tests/utilities/test_agent_utils.py Fixed
Comment thread lib/crewai/tests/utilities/test_agent_utils.py Fixed
Comment thread lib/crewai/src/crewai/utilities/agent_utils.py
- Re-raise AgentRepositoryError before the generic except handler so
  security error messages are not swallowed (Cursor Bugbot feedback)
- Move module import to top-level to fix mixed import style warning
- Replace unnecessary lambda with MagicMock class reference
- Add noqa for PERF203 on the new except clause

Co-Authored-By: João <joao@crewai.com>
import pytest
from pydantic import BaseModel, Field

import crewai.utilities.agent_utils as _agent_utils_mod
Co-Authored-By: João <joao@crewai.com>
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 3c8d2d2. Configure here.

ALLOWED_TOOL_MODULE_PREFIXES: Final[tuple[str, ...]] = (
"crewai_tools.",
"crewai.tools.",
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allowlist rejects the actual production module path format

High Severity

The ALLOWED_TOOL_MODULE_PREFIXES only contains "crewai_tools." and "crewai.tools." (with trailing dots), but the existing tests in test_agent.py (lines 2086, 2091, 2123, 2147) show the CrewAI Plus API actually returns "module": "crewai_tools" (without a trailing dot or submodule) for tools like SerperDevTool and FileReadTool. Since "crewai_tools".startswith("crewai_tools.") is False, all legitimate tool imports using the bare package name will be rejected as untrusted, breaking repository agent loading in production.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3c8d2d2. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant