crewAIInc/crewAI · error · ValueError
Blocked unsafe {label}: {e}
Error message
Blocked unsafe {label}: {e} What it means
Raised by RAGTool.add() when a URL argument fails CrewAI's SSRF guard (validate_url). Every URL-scheme argument is checked before being handed to the storage layer; if the URL targets a blocked host (loopback/private/reserved IPs, non-http(s) schemes, credentials in URL, etc.) the guard's reason is wrapped as 'Blocked unsafe URL: <reason>'. The {label} variant appears in the shared _check_url/_check_path helpers used for typed content items.
Source
Thrown at lib/crewai-tools/src/crewai_tools/tools/rag/rag_tool.py:288
# Keyword argument (documented API)
rag_tool.add(path="path/to/document.pdf", data_type="file")
rag_tool.add(file_path="path/to/document.pdf", data_type="pdf_file")
# Auto-detect type from extension
rag_tool.add("path/to/document.pdf") # auto-detects PDF
"""
# Validate file paths and URLs before adding to prevent
# unauthorized file reads and SSRF.
from urllib.parse import urlparse
from crewai_tools.security.safe_path import validate_file_path, validate_url
def _check_url(value: str, label: str) -> None:
try:
validate_url(value)
except ValueError as e:
raise ValueError(f"Blocked unsafe {label}: {e}") from e
def _check_path(value: str, label: str) -> str:
try:
return validate_file_path(value)
except ValueError as e:
raise ValueError(f"Blocked unsafe {label}: {e}") from e
validated_args: list[ContentItem] = []
for arg in args:
source_ref = (
str(arg.get("source", arg.get("content", "")))
if isinstance(arg, dict)
else str(arg)
)
# Check if it's a URL — only catch urlparse-specific errors here;
# validate_url's ValueError must propagate so it is never silently bypassed.
try:View on GitHub (pinned to 754d7323be)
Solutions
- If the target is genuinely safe, ingest its content directly: fetch it yourself from the allowed network context and pass the text/blob via a content item instead of a URL
- Check the wrapped reason (e) — it states exactly why the URL was rejected
- For local testing, expose the content via a public tunnel or write it to a file and add the file path instead
- Never disable the guard in shared/production code — it exists to stop agent-driven SSRF
Example fix
# before
rag_tool.add('http://localhost:8000/docs/index.html') # ValueError: Blocked unsafe URL
# after
import requests
text = requests.get('http://localhost:8000/docs/index.html', timeout=10).text
rag_tool.add({'content': text}) # plain content bypasses URL fetching entirely
Defensive patterns
Strategy: validation
Validate before calling
from crewai_tools.security.safe_path import validate_url
def url_safe(u: str) -> bool:
try:
validate_url(u)
return True
except ValueError:
return False
assert url_safe(target_url), f"URL rejected by SSRF guard: {target_url}" Try / catch
try:
rag_tool.add(url)
except ValueError as e:
if str(e).startswith("Blocked unsafe"):
# fetch content from an allowed context and add as text instead
text = fetch_via_proxy(url)
rag_tool.add({"content": text})
else:
raise Prevention
- Pre-validate URLs with crewai_tools.security.safe_path.validate_url before calling add()
- Never point RAG ingestion at loopback/private/metadata IPs
- Pass local file content as {'content': text} rather than internal URLs
- Treat 'Blocked unsafe' as a security decision, not a bug to work around
When it happens
Trigger: Calling rag_tool.add('http://169.254.169.254/latest/meta-data') or any URL resolving to loopback/private/reserved ranges, non-http(s) schemes, or otherwise disallowed targets — enforced by crewai_tools.security.safe_path.validate_url before ingestion.
Common situations: Agents instructed to fetch internal endpoints (localhost dev servers, 10.x/192.168.x hosts, cloud metadata IPs); legitimate intranet ingestion being blocked by the SSRF guard; passing URLs with embedded userinfo.
Related errors
- Blocked unsafe URL: {e}
- file:// URLs are not allowed: '{url}'. Use a file path inste
- URL scheme '{parsed.scheme}' is not allowed. Only http and h
- URL has no hostname: '{url}'
- URL '{url}' resolves to private/reserved IP {ip_str}. Access
AI-assisted analysis of crewAIInc/crewAI@754d7323be (2026-08-15).
Data as JSON: /api/errors/c87fec8808b506b8.
Report an issue: GitHub.