Skip to content

fix(tools): re-validate redirects and pin peer IP to close SSRF bypass#6038

Draft
theCyberTech wants to merge 2 commits into
mainfrom
worktree-ssrf-redirect-fix
Draft

fix(tools): re-validate redirects and pin peer IP to close SSRF bypass#6038
theCyberTech wants to merge 2 commits into
mainfrom
worktree-ssrf-redirect-fix

Conversation

@theCyberTech
Copy link
Copy Markdown
Member

Summary

validate_url (crewai_tools/security/safe_path.py) inspects the URL string it is handed, but ScrapeWebsiteTool and ScrapeElementFromWebsiteTool then fetched with requests.get(..., allow_redirects=True). Two gaps followed from that decoupling:

  1. Redirect bypass — a normal-looking public host that returns 302 Location: <internal-address> was followed to the internal target without re-validation, turning the tools into an SSRF proxy for the worker's network (incl. RFC1918 ranges and 169.254.169.254 cloud metadata, which the validator's own blocklist explicitly covers).
  2. DNS-rebinding TOCTOUvalidate_url resolved the host via getaddrinfo, then discarded the IPs and returned the URL string; requests re-resolved at connect time, so the IP that was checked was not necessarily the IP that was used.

This is a bypass of a CrewAI-shipped SSRF control (the validator was introduced as a security fix, CVE-2026-2286).

Fix

New crewai_tools/security/safe_requests.py validates at the connection layer, where the actual connection is made:

  • SSRFProtectedAdapter.send() re-runs validate_url on every request. requests.Session.send invokes the adapter once per redirect hop, so each Location is validated before it is followed → closes the redirect arm.
  • _SafeHTTP[S]Connection validate the actual connected peer IP (getpeername()) immediately after connect(). The IP that was authorised is the IP the socket uses → closes the DNS-rebinding gap.
  • safe_get() is a drop-in replacement for requests.get. The CREWAI_TOOLS_ALLOW_UNSAFE_PATHS escape hatch is honored consistently.

ScrapeWebsiteTool and ScrapeElementFromWebsiteTool now fetch through safe_get.

Scope

  • Limited to the two confirmed worker-side direct-fetch tools. The vendor-forwarding tools (Jina/Serper/Firecrawl/etc.) hand the URL to a third-party API, so the redirect lands on the vendor's network — a separate concern, not changed here.
  • WebsiteSearchTool/RagTool fetch through the RAG/embedchain loader layer (not a direct requests.get); that path is not covered by this requests-based utility and is flagged for a follow-up audit.

Testing

  • tests/utilities/test_safe_requests.py (new): per-hop redirect re-validation, connection peer guard (private/metadata blocked, public allowed, escape hatch, simulated rebind), adapter mounting.
  • pytest tests/utilities/test_safe_requests.py tests/utilities/test_safe_path.py → 35 passed.
  • ruff check clean.
  • End-to-end smoke: ScrapeWebsiteTool._run(website_url="http://169.254.169.254/latest/meta-data/") is now blocked.

🤖 Generated with Claude Code

validate_url checked the URL string but ScrapeWebsiteTool and
ScrapeElementFromWebsiteTool then fetched with requests' default
allow_redirects=True, so a public host that 302-redirected to an
internal address reached it without re-validation. The resolved IPs
were also discarded, leaving a DNS time-of-check/time-of-use gap.

Add crewai_tools.security.safe_requests:
- SSRFProtectedAdapter re-runs validate_url on every send, including
  each redirect hop (Session.send calls the adapter per hop).
- Connections validate the actual connected peer IP at connect time,
  so the IP that was authorised is the IP that is used (closes the
  DNS-rebinding gap).

Route the two direct-fetch scrape tools through safe_get and add tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 4, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 963f415b-bd05-443f-bb74-80e7938e3270

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-ssrf-redirect-fix

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added the size/L label Jun 4, 2026
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