fix(tools): block private URLs in SeleniumScrapingTool - #6870
Conversation
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.
📝 WalkthroughWalkthroughThe 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. ChangesSelenium SSRF protection
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
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
lib/crewai-tools/tests/tools/selenium_scraping_tool_test.py (2)
156-165: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winVerify that rejected fixed URLs do not create a WebDriver.
The test checks only that
SeleniumScrapingToolraisesValueError. It still passes whenselenium.webdriver.Chromewas called before validation failed. Addmocked_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 winMake the SSRF regression assertions specific.
The
or "Error scraping website" in resultbranch accepts unrelated failures. It does not prove thatvalidate_urlrejected the target. Assert the private/reserved validation error only.These tests call
_rundirectly, so they also bypassSeleniumScrapingToolSchema.validate_website_url. Add a schema-level test forSeleniumScrapingToolSchemawith a loopback or metadata URL.As per coding guidelines,
**/*test*.pyfiles 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
📒 Files selected for processing (2)
lib/crewai-tools/src/crewai_tools/tools/selenium_scraping_tool/selenium_scraping_tool.pylib/crewai-tools/tests/tools/selenium_scraping_tool_test.py
| 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." |
There was a problem hiding this comment.
🩺 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' || trueRepository: 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)
PYRepository: 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.
| # Defense in depth for the fixed-URL schema path (no pydantic re-check). | ||
| url = validate_url(url) | ||
|
|
There was a problem hiding this comment.
🔒 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 -SRepository: 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 -SRepository: 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.pyRepository: 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.
Summary
SeleniumScrapingToolaccepted anyhttp(s)URL and navigated a local Chrome WebDriver to it, with no private/reserved IP checks.FirecrawlScrapeWebsiteTool,ScrapflyScrapeWebsiteTool) already callcrewai_tools.security.safe_path.validate_url._make_request(defense in depth for the fixed-URL schema path).Impact
An LLM-controlled
website_urlcould 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
127.0.0.1,169.254.169.254, and fixed-URLlocalhostMade with Cursor