Skip to content

fix: add SSRF and path traversal protections#5315

Merged
joaomdmoura merged 9 commits into
mainfrom
fix/cve-ssrf-file-read
Apr 7, 2026
Merged

fix: add SSRF and path traversal protections#5315
joaomdmoura merged 9 commits into
mainfrom
fix/cve-ssrf-file-read

Conversation

@greysonlalonde
Copy link
Copy Markdown
Contributor

@greysonlalonde greysonlalonde commented Apr 7, 2026

Summary

Addresses CVE-2026-2285 and CVE-2026-2286.

  • validate_url blocks non-http/https schemes, private IPs, loopback, link-local, reserved addresses via DNS resolution. Applied to 11 web/scraping tools.
  • validate_path confines file access to the working directory via Path.resolve() + is_relative_to(). Applied to 7 file/directory tools.

Test plan

  • validate_url blocks file://, http://127.0.0.1, http://169.254.169.254
  • validate_url allows https://example.com
  • validate_path blocks /etc/passwd, ../../etc/passwd
  • validate_path allows relative paths within CWD
  • 242 existing crewai-tools tests pass

Note

Medium Risk
Touches many tools’ runtime inputs and adds DNS-based URL blocking, which can change behavior for previously-accepted paths/hosts and may fail in restricted-network environments. Changes are security-focused and localized but affect common scraping/file-read flows.

Overview
Adds centralized input hardening for tool-supplied file paths and URLs. A new crewai_tools.security.safe_path module validates file/directory paths by resolving symlinks and enforcing a base_dir boundary (defaulting to CWD), and validates URLs by only allowing http/https, blocking file://, and rejecting hosts that resolve to private/reserved IP ranges (SSRF protection).

These validations are now applied across multiple file, directory, RAG ingestion, and web-scraping tools (e.g., FileReadTool, DirectoryReadTool, RagTool.add, Firecrawl/Hyperbrowser/Jina/Scrapfly/Serper/Serply, OCR/Vision), with RagTool.add also rewriting validated path inputs to their resolved form to reduce symlink TOCTOU risk. The prior utilities.safe_path implementation is replaced with a backward-compatible re-export, and tests are updated to target the new module.

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

@github-actions github-actions Bot added the size/M label Apr 7, 2026
Copy link
Copy Markdown
Contributor

@iris-clawd iris-clawd left a comment

Choose a reason for hiding this comment

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

Review: SSRF & Path Traversal Protections

Good security hardening overall — centralized validators applied consistently across tools. A few things to flag:

🔴 DNS Rebinding (validate_url)

The DNS resolution at validation time does not pin the resolved IP for the subsequent HTTP request. An attacker can serve a safe IP on the first lookup (validation pass) and a private IP on the second lookup (when requests.get/post resolves again). This is the classic DNS rebinding bypass for SSRF checks.

Mitigation options:

  1. Return the resolved IP and force the HTTP client to connect to it directly (e.g., override the Host header or use a custom urllib3 adapter)
  2. Pin the resolution with a very short-lived DNS cache per request
  3. At minimum, document this limitation so we know it's a conscious tradeoff

🟡 is_private may be overly broad

ipaddress.is_private blocks RFC 1918, CGNAT (100.64.0.0/10), and Tailscale ranges (100.x.x.x). Users with crews that need to hit internal APIs (common in enterprise/Factory deployments) will get hard failures with no recourse. Consider:

  • An opt-out flag or allowlist parameter on validate_url (e.g., allowed_hosts)
  • Or at least document this as a known breaking change for internal-network use cases

🟡 Path validation TOCTOU

validate_path() resolves and checks the path, but the actual file open happens later. A symlink swap between validation and open could bypass the check. Low severity for most use cases, but worth noting in a security-focused PR. The canonical fix is to open() the file first, then check the fd's real path via os.fstat//proc/self/fd, but that's heavier.

🟢 Minor nits

  • security/__init__.py is empty — consider re-exporting validate_url and validate_path for cleaner imports
  • files_compressor_tool.py validates input_path but not any output path — if the output is user-controlled, that's a write-to-arbitrary-location gap
  • brightdata_unlocker.py: validates url (correct — it's the user-supplied target), but the actual HTTP call goes to self.base_url. The validation is on the right value; just noting the indirection.

✅ What looks good

  • Clean separation into security/safe_path.py and security/safe_url.py
  • Consistent application across all relevant tools (11 URL, 7 path)
  • Dropping the unused docker dep is a nice cleanup
  • Test plan covers the key cases

The DNS rebinding gap is the main one I'd want addressed or explicitly documented before merge. The rest are lower priority. Nice work on this, Greyson. 💬 158

Copy link
Copy Markdown
Contributor

@iris-clawd iris-clawd left a comment

Choose a reason for hiding this comment

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

Approving per request. The DNS rebinding and is_private breadth notes from my earlier review still stand as suggestions for future iteration, but the overall security posture improvement is solid and worth landing. ✅

@alex-clawd alex-clawd force-pushed the fix/cve-ssrf-file-read branch from 4ec363d to 6b49989 Compare April 7, 2026 16:55
@alex-clawd
Copy link
Copy Markdown
Contributor

Rebased on main and unified the security utilities. Here's what changed:

Kept from your PR (the good stuff):

  • All 18 per-tool validation calls — this wider coverage is great and extends beyond the RAG chokepoint

Unified with the already-merged #5310:

  • Restored utilities/safe_path.py (our comprehensive implementation with env var escape hatch, IPv4/IPv6 separation, DNS rebinding protection)
  • Restored the centralized RagTool.add() validation as the chokepoint for all RAG tools
  • Removed security/ directory to avoid duplicate implementations
  • Updated all per-tool imports to use from crewai_tools.utilities.safe_path import validate_file_path, validate_url

Result: Single source of truth for path/URL validation, both centralized (RAG chokepoint) and distributed (per-tool) protection layers. 37 tests passing, lint clean.

greysonlalonde and others added 7 commits April 7, 2026 10:16
CVE-2026-2286: validate_url blocks non-http/https schemes, private
IPs, loopback, link-local, reserved addresses. Applied to 11 web tools.

CVE-2026-2285: validate_path confines file access to the working
directory. Applied to 7 file and directory tools.
Rewrite validated URLs to use the resolved IP, preventing DNS rebinding
between validation and request time. SDK-based tools use pin_ip=False
since they manage their own HTTP clients. Add allow_private flag for
deployments that need internal network access.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move safe_path.py to crewai_tools/security/; add safe_url.py re-export
- Keep utilities/safe_path.py as a backwards-compat shim
- Update all 21 import sites to use crewai_tools.security.safe_path
- files_compressor_tool: validate output_path (user-controlled)
- serper_scrape_website_tool: call validate_url() before building payload
- brightdata_unlocker: validate_url() already called without assignment (no-op fix)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…mpat shim

- security/safe_path.py is the canonical location for all validation
- utilities/safe_path.py re-exports for backward compatibility
- All tool imports already point to security.safe_path
- All review comments already addressed in prior commits
… validator

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@alex-clawd alex-clawd force-pushed the fix/cve-ssrf-file-read branch from b60d547 to 2b39baa Compare April 7, 2026 17:18
Comment thread lib/crewai-tools/src/crewai_tools/tools/file_read_tool/file_read_tool.py Outdated
Comment thread lib/crewai-tools/src/crewai_tools/security/safe_url.py Outdated
alex-clawd and others added 2 commits April 7, 2026 10:30
…move unused safe_url.py

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@joaomdmoura joaomdmoura merged commit 868416b into main Apr 7, 2026
43 of 44 checks passed
@joaomdmoura joaomdmoura deleted the fix/cve-ssrf-file-read branch April 7, 2026 17:44
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 4f5a9f7. Configure here.

Returns:
Base64-encoded image data
"""
image_path = validate_file_path(image_path)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Validation errors swallowed by try/except in VisionTool

Medium Severity

The validate_file_path call within _encode_image is wrapped by try/except Exception blocks. This catches validation errors and converts them into a generic error string, preventing security exceptions from propagating. This differs from the approach taken for other tools in the same PR, where validation was moved outside these blocks.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4f5a9f7. Configure here.

volkanozyildirim pushed a commit to volkanozyildirim/crew-ai that referenced this pull request Apr 15, 2026
* fix: add SSRF and path traversal protections

CVE-2026-2286: validate_url blocks non-http/https schemes, private
IPs, loopback, link-local, reserved addresses. Applied to 11 web tools.

CVE-2026-2285: validate_path confines file access to the working
directory. Applied to 7 file and directory tools.

* fix: drop unused assignment from validate_url call

* fix: DNS rebinding protection and allow_private flag

Rewrite validated URLs to use the resolved IP, preventing DNS rebinding
between validation and request time. SDK-based tools use pin_ip=False
since they manage their own HTTP clients. Add allow_private flag for
deployments that need internal network access.

* fix: unify security utilities and restore RAG chokepoint validation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: move validation to security/ package + address review comments

- Move safe_path.py to crewai_tools/security/; add safe_url.py re-export
- Keep utilities/safe_path.py as a backwards-compat shim
- Update all 21 import sites to use crewai_tools.security.safe_path
- files_compressor_tool: validate output_path (user-controlled)
- serper_scrape_website_tool: call validate_url() before building payload
- brightdata_unlocker: validate_url() already called without assignment (no-op fix)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: move validation to security/ package, keep utilities/ as compat shim

- security/safe_path.py is the canonical location for all validation
- utilities/safe_path.py re-exports for backward compatibility
- All tool imports already point to security.safe_path
- All review comments already addressed in prior commits

* fix: move validation outside try/except blocks, use correct directory validator

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: use resolved paths from validation to prevent symlink TOCTOU, remove unused safe_url.py

---------

Co-authored-by: Alex <alex@crewai.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants