crewAIInc/crewAI · error · ValueError
URL ' ' resolves to private/reserved IP . Access to…
Error message
URL '{url}' resolves to private/reserved IP {ip_str}. Access to internal networks is not allowed. Set {_UNSAFE_PATHS_ENV}=true to bypass. What it means
The core SSRF protection in crewai_tools: after resolving a URL's hostname, every returned address is checked against private/reserved ranges (loopback, RFC1918, link-local, etc.). If any resolved IP is private or reserved, the request is refused with this ValueError. This prevents agents from being tricked into hitting internal services (e.g. cloud metadata at 169.254.169.254).
Solutions
- If the target is a legitimate internal/local service you control, set the documented bypass env var (e.g. CREWAI_TOOLS_UNSAFE_PATHS/true per _UNSAFE_PATHS_ENV) only in that trusted environment.
- Point the tool at the public address of the service instead of the private one.
- If this appears while processing untrusted web content, treat it as the guard working as intended — do not bypass; audit where the URL came from.
- For local testing, prefer passing a file path (for file-backed tools) over a localhost URL.
Example fix
# before (guard blocks internal target)
tool.run("http://localhost:8000/report")
# after (explicit opt-in for trusted local testing only)
# export CREWAI_UNSAFE_PATHS=true (name per _UNSAFE_PATHS_ENV in safe_path.py)
tool.run("http://localhost:8000/report") Defensive patterns
Strategy: try-catch
Validate before calling
import ipaddress, socket
from urllib.parse import urlparse
def resolves_to_public_only(url: str) -> bool:
host = urlparse(url).hostname
if not host:
return False
try:
infos = socket.getaddrinfo(host, None)
except socket.gaierror:
return False
ips = {ipaddress.ip_address(i[4][0]) for i in infos}
return all(ip.is_global for ip in ips) Try / catch
try:
fetch_url_body(url, max_bytes=1_000_000)
except ValueError as e:
if "private/reserved IP" in str(e):
# treat as untrusted content; log and skip rather than bypass
logger.warning("Blocked internal-network URL: %s", url)
return None
raise Prevention
- Treat this error as a security signal, not an obstacle — never blanket-disable the guard in production.
- Pre-filter agent-supplied URLs through your own is_global IP check before invoking fetch tools.
- If local testing needs a bypass, scope the env var to the test process only and never ship it.
When it happens
Trigger: A URL pointing at 'http://localhost:8080/', 'http://192.168.1.10/api', 'http://169.254.169.254/latest/meta-data/', or a public-looking hostname whose DNS has records that resolve to internal IPs (DNS rebinding or split-horizon DNS).
Common situations: LLM-driven fetch tools following links embedded in scraped content that target internal addresses; developers testing against local dev servers (localhost:8000) through a guarded tool; corporate split-horizon DNS where an internal name resolves differently inside the network.
Related errors
- Blocked unsafe
- Blocked unsafe URL
- Could not resolve hostname
- file:// URLs are not allowed
- URL has no hostname
AI-assisted analysis of crewAIInc/crewAI@754d7323be (2026-08-15).
Data as JSON: /api/errors/31b8a0413a9d81fb.
Report an issue: GitHub.
Appendix: source
Thrown at lib/crewai-tools/src/crewai_tools/security/safe_path.py:236
if parsed.scheme not in ("http", "https"):
raise ValueError(
f"URL scheme '{parsed.scheme}' is not allowed. Only http and https are supported."
)
if not parsed.hostname:
raise ValueError(f"URL has no hostname: '{url}'")
try:
addrinfos = socket.getaddrinfo(
parsed.hostname, parsed.port or (443 if parsed.scheme == "https" else 80)
)
except socket.gaierror as exc:
raise ValueError(f"Could not resolve hostname: '{parsed.hostname}'") from exc
for _family, _, _, _, sockaddr in addrinfos:
ip_str = str(sockaddr[0])
if _is_private_or_reserved(ip_str):
raise ValueError(
f"URL '{url}' resolves to private/reserved IP {ip_str}. "
f"Access to internal networks is not allowed. "
f"Set {_UNSAFE_PATHS_ENV}=true to bypass."
)
return url
View on GitHub (pinned to 754d7323be)