Skip to content

fix(tools): block private URLs in SeleniumScrapingTool - #6870

Open
SashaMIT wants to merge 1 commit into
crewAIInc:mainfrom
SashaMIT:codered/selenium-ssrf-validate-url
Open

fix(tools): block private URLs in SeleniumScrapingTool#6870
SashaMIT wants to merge 1 commit into
crewAIInc:mainfrom
SashaMIT:codered/selenium-ssrf-validate-url

Conversation

@SashaMIT

@SashaMIT SashaMIT commented Aug 8, 2026

Copy link
Copy Markdown

Summary

  • SeleniumScrapingTool accepted any http(s) URL and navigated a local Chrome WebDriver to it, with no private/reserved IP checks.
  • Sibling scrape tools (FirecrawlScrapeWebsiteTool, ScrapflyScrapeWebsiteTool) already call crewai_tools.security.safe_path.validate_url.
  • Route Selenium URLs through the same helper at schema validation, fixed-URL construction, and _make_request (defense in depth for the fixed-URL schema path).

Impact

An LLM-controlled website_url could reach loopback, RFC1918, link-local, or cloud metadata (169.254.169.254) from the host running the crew, enabling SSRF / metadata credential theft when this tool is attached.

Test plan

  • Added unit tests rejecting 127.0.0.1, 169.254.169.254, and fixed-URL localhost
  • CI selenium scraping tool tests

Made with Cursor

Route website_url through validate_url (same helper Firecrawl/Scrapfly
already use) so LLM-controlled navigations cannot hit loopback, RFC1918,
or cloud metadata endpoints via the local Chrome WebDriver.
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The Selenium scraping tool now validates URLs against private and reserved SSRF targets during schema validation, construction, and request execution. Tests cover loopback, cloud metadata, and fixed localhost URLs.

Changes

Selenium SSRF protection

Layer / File(s) Summary
URL validation paths
lib/crewai-tools/src/crewai_tools/tools/selenium_scraping_tool/selenium_scraping_tool.py
The tool applies validate_url during schema validation, fixed-URL initialization, and request execution before browser navigation.
SSRF regression tests
lib/crewai-tools/tests/tools/selenium_scraping_tool_test.py
Tests reject loopback, cloud metadata, and fixed localhost targets. Navigation is not called for rejected request URLs.

Sequence Diagram(s)

sequenceDiagram
  participant SeleniumScrapingTool
  participant validate_url
  participant WebDriver
  SeleniumScrapingTool->>validate_url: Validate website URL
  validate_url-->>SeleniumScrapingTool: Reject private or reserved target
  SeleniumScrapingTool->>WebDriver: Navigate only after validation
Loading

Suggested reviewers: greysonlalonde

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: blocking private URLs in SeleniumScrapingTool.
Description check ✅ Passed The description explains the SSRF risk, implementation scope, affected tool, and test coverage.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
lib/crewai-tools/tests/tools/selenium_scraping_tool_test.py (2)

156-165: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Verify that rejected fixed URLs do not create a WebDriver.

The test checks only that SeleniumScrapingTool raises ValueError. It still passes when selenium.webdriver.Chrome was called before validation failed. Add mocked_chrome.assert_not_called() after the exception assertion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai-tools/tests/tools/selenium_scraping_tool_test.py` around lines 156
- 165, Update test_fixed_url_constructor_rejects_private_targets to assert
mocked_chrome.assert_not_called() after confirming the ValueError, ensuring
invalid fixed URLs are rejected before WebDriver creation.

139-142: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Make the SSRF regression assertions specific.

The or "Error scraping website" in result branch accepts unrelated failures. It does not prove that validate_url rejected the target. Assert the private/reserved validation error only.

These tests call _run directly, so they also bypass SeleniumScrapingToolSchema.validate_website_url. Add a schema-level test for SeleniumScrapingToolSchema with a loopback or metadata URL.

As per coding guidelines, **/*test*.py files must write unit tests for new functionality and focus on behavior rather than implementation details.

Suggested assertion change
-    assert "private/reserved" in result.lower() or "Error scraping website" in result
+    assert "private/reserved" in result.lower()

Also applies to: 150-153

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai-tools/tests/tools/selenium_scraping_tool_test.py` around lines 139
- 142, Make the SSRF regression tests assert specifically that loopback/private
URL validation returns the “private/reserved” error, removing the unrelated
“Error scraping website” fallback while retaining the driver-not-called check.
Add a schema-level test for SeleniumScrapingToolSchema.validate_website_url
covering a loopback or metadata URL, validating rejection behavior without
testing implementation details.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@lib/crewai-tools/src/crewai_tools/tools/selenium_scraping_tool/selenium_scraping_tool.py`:
- Around line 202-204: Update the Selenium browser flow around validate_url and
driver.get to enforce SSRF checks at Chrome egress, not only on the initial URL.
Configure the local WebDriver host or request interception to validate every
navigation, redirect, and subresource destination against the existing SSRF
policy, including DNS-rebinding targets, before allowing the request.
- Around line 128-130: Update SeleniumScrapingTool.__init__ so fixed website
URLs are validated before webdriver.Chrome is initialized, preventing driver
creation when validate_url raises ValueError. In
lib/crewai-tools/tests/tools/selenium_scraping_tool_test.py lines 156-165,
extend the fixed-URL constructor regression test to assert
mocked_chrome.assert_not_called() after confirming the ValueError.

---

Nitpick comments:
In `@lib/crewai-tools/tests/tools/selenium_scraping_tool_test.py`:
- Around line 156-165: Update test_fixed_url_constructor_rejects_private_targets
to assert mocked_chrome.assert_not_called() after confirming the ValueError,
ensuring invalid fixed URLs are rejected before WebDriver creation.
- Around line 139-142: Make the SSRF regression tests assert specifically that
loopback/private URL validation returns the “private/reserved” error, removing
the unrelated “Error scraping website” fallback while retaining the
driver-not-called check. Add a schema-level test for
SeleniumScrapingToolSchema.validate_website_url covering a loopback or metadata
URL, validating rejection behavior without testing implementation details.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8f4c4289-b4e5-49fd-985c-8a8784a1a337

📥 Commits

Reviewing files that changed from the base of the PR and between 92012ae and 643e627.

📒 Files selected for processing (2)
  • lib/crewai-tools/src/crewai_tools/tools/selenium_scraping_tool/selenium_scraping_tool.py
  • lib/crewai-tools/tests/tools/selenium_scraping_tool_test.py

Comment on lines +128 to +130
self.website_url = validate_url(website_url)
self.description = (
f"A tool that can be used to read {website_url}'s content."
f"A tool that can be used to read {self.website_url}'s content."

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate files =="
git ls-files | rg 'selenium_scraping_tool\.py$|selenium_scraping_tool_test\.py$' || true

echo
echo "== production file outline =="
ast-grep outline lib/crewai-tools/src/crewai_tools/tools/selenium_scraping_tool/selenium_scraping_tool.py --view expanded | sed -n '1,220p' || true

echo
echo "== target production lines =="
cat -n lib/crewai-tools/src/crewai_tools/tools/selenium_scraping_tool/selenium_scraping_tool.py | sed -n '1,190p'

echo
echo "== target test lines =="
cat -n lib/crewai-tools/tests/tools/selenium_scraping_tool_test.py | sed -n '130,180p'

echo
echo "== search validate_url and __init__ usage =="
rg -n "def __init__|webdriver\.Chrome|validate_url|def validate_url|private/reserved|localhost" lib/crewai-tools/src/crewai-tools/tools/lib/crewai-tools -g '*.py' || true

Repository: crewAIInc/crewAI

Length of output: 11238


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== validate_url definitions =="
git ls-files | rg 'safe_path|security' | xargs -r rg -n "def validate_url|is_private|localhost|private/reserved|validate_url" -g '*.py' || true

echo
echo "== imports of safe_path == rg =="
rg -n "from .*safe_path import|import .*safe_path|validate_url\(" lib/crewai-tools/src/crewai-tools -g '*.py' || true

echo
echo "== constructor ordering verifier =="
python3 - <<'PY'
from pathlib import Path
src = Path('lib/crewai-tools/src/crewai_tools/tools/selenium_scraping_tool/selenium_scraping_tool.py')
text = src.read_text()

webdriver_line = next(
    (i for i, line in enumerate(text.splitlines(), 1) if 'webdriver.Chrome(' in line),
    None
)
validate_init_line = next(
    (i for i, line in enumerate(text.splitlines(), 1) if 'validate_url(website_url)' in line),
    None
)

print(f"webdriver.Chrome line: {webdriver_line}")
print(f"validate_url(website_url) line: {validate_init_line}")
print(f"constructor validation runs after Chrome initialization: {validate_init_line is not None and webdriver_line is not None and validate_init_line > webdriver_line}")

pytest = Path('lib/crewai-tools/tests/tools/selenium_scraping_tool_test.py')
test = pytest.read_text().splitlines()
start = next((i for i, line in enumerate(test, 1) if 'test_fixed_url_constructor_rejects_private_targets' in line), None)
print(f"test start line: {start}")
if start:
    snippet = '\n'.join(test[start-1:start+12])
    print("contains mocked_chrome.assert_not_called():", 'mocked_chrome.assert_not_called()' in snippet)
PY

Repository: crewAIInc/crewAI

Length of output: 50372


Validate fixed URLs before creating the Selenium WebDriver.

SeleniumScrapingTool.__init__ creates webdriver.Chrome before validate_url(website_url) runs for a fixed URL. Move the validation before driver initialization, or close the driver when validation fails. The fixed-URL constructor regression test should also assert mocked_chrome.assert_not_called() after confirming the ValueError.

📍 Affects 2 files
  • lib/crewai-tools/src/crewai_tools/tools/selenium_scraping_tool/selenium_scraping_tool.py#L128-L130 (this comment)
  • lib/crewai-tools/tests/tools/selenium_scraping_tool_test.py#L156-L165
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@lib/crewai-tools/src/crewai_tools/tools/selenium_scraping_tool/selenium_scraping_tool.py`
around lines 128 - 130, Update SeleniumScrapingTool.__init__ so fixed website
URLs are validated before webdriver.Chrome is initialized, preventing driver
creation when validate_url raises ValueError. In
lib/crewai-tools/tests/tools/selenium_scraping_tool_test.py lines 156-165,
extend the fixed-URL constructor regression test to assert
mocked_chrome.assert_not_called() after confirming the ValueError.

Comment on lines +202 to +204
# Defense in depth for the fixed-URL schema path (no pydantic re-check).
url = validate_url(url)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
git ls-files | rg 'selenium_scraping_tool\.py$|selenium' || true

echo "== target outline =="
ast-grep outline lib/crewai-tools/src/crewai_tools/tools/selenium_scraping_tool/selenium_scraping_tool.py --view expanded || true

echo "== relevant source =="
sed -n '1,260p' lib/crewai-tools/src/crewai_tools/tools/selenium_scraping_tool/selenium_scraping_tool.py

echo "== validate_url definitions/usages =="
rg -n "def validate_url|validate_url|SeleniumBase|Crawler|driver\.get|driver\.add_" lib/crewai-tools/src/crewai_tools/tools/selenium_scraping_tool -S

Repository: crewAIInc/crewAI

Length of output: 17379


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== safe_path outline =="
ast-grep outline lib/crewai-tools/src/crewai_tools/security/safe_path.py --view expanded || true

echo "== safe_path source =="
sed -n '1,220p' lib//crewai_tools/src/crewai_tools/security/safe_path.py || true

echo "== validate_url search =="
rg -rn "def validate_url|resolve_hostname|ipaddress|ALLOWED|BLOCK|private|reserved|safe_path" lib/crewai-tools/src/crewai_tools -S

Repository: crewAIInc/crewAI

Length of output: 9401


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== safe_path exact source =="
sed -n '140,210p' lib/crewai-tools/src/crewai_tools/security/safe_path.py

echo "== selenium source around driver navigation =="
sed -n '193,215p' lib/crewai-tools/src/crewai_tools/tools/selenium_scraping_tool/selenium_scraping_tool.py

Repository: crewAIInc/crewAI

Length of output: 3379


Enforce the SSRF policy at Chrome egress.

validate_url(url) before driver.get(url) only checks the initial URL once. Chrome can still follow redirects and load page subresources outside Python’s reach, including DNS-rebinding targets. Apply an outbound SSRF policy on the local WebDriver host or intercept/validate every browser request.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@lib/crewai-tools/src/crewai_tools/tools/selenium_scraping_tool/selenium_scraping_tool.py`
around lines 202 - 204, Update the Selenium browser flow around validate_url and
driver.get to enforce SSRF checks at Chrome egress, not only on the initial URL.
Configure the local WebDriver host or request interception to validate every
navigation, redirect, and subresource destination against the existing SSRF
policy, including DNS-rebinding targets, before allowing the request.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant