{"record":{"id":"31b8a0413a9d81fb","repo":"crewAIInc/crewAI","slug":"url-url-resolves-to-private-reserved-ip-ip-st","errorCode":null,"errorMessage":"URL '{url}' resolves to private/reserved IP {ip_str}. Access to internal networks is not allowed. Set {_UNSAFE_PATHS_ENV}=true to bypass.","messagePattern":"URL '(.+?)' resolves to private/reserved IP (.+?)\\. Access to internal networks is not allowed\\. Set (.+?)=true to bypass\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"lib/crewai-tools/src/crewai_tools/security/safe_path.py","lineNumber":236,"sourceCode":"    if parsed.scheme not in (\"http\", \"https\"):\n        raise ValueError(\n            f\"URL scheme '{parsed.scheme}' is not allowed. Only http and https are supported.\"\n        )\n\n    if not parsed.hostname:\n        raise ValueError(f\"URL has no hostname: '{url}'\")\n\n    try:\n        addrinfos = socket.getaddrinfo(\n            parsed.hostname, parsed.port or (443 if parsed.scheme == \"https\" else 80)\n        )\n    except socket.gaierror as exc:\n        raise ValueError(f\"Could not resolve hostname: '{parsed.hostname}'\") from exc\n\n    for _family, _, _, _, sockaddr in addrinfos:\n        ip_str = str(sockaddr[0])\n        if _is_private_or_reserved(ip_str):\n            raise ValueError(\n                f\"URL '{url}' resolves to private/reserved IP {ip_str}. \"\n                f\"Access to internal networks is not allowed. \"\n                f\"Set {_UNSAFE_PATHS_ENV}=true to bypass.\"\n            )\n\n    return url\n","sourceCodeStart":218,"sourceCodeEnd":243,"githubUrl":"https://github.com/crewAIInc/crewAI/blob/754d7323beb2fd042e33444a115ea2d5a47193f0/lib/crewai-tools/src/crewai_tools/security/safe_path.py#L218-L243","documentation":"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).","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"# before (guard blocks internal target)\ntool.run(\"http://localhost:8000/report\")\n\n# after (explicit opt-in for trusted local testing only)\n# export CREWAI_UNSAFE_PATHS=true  (name per _UNSAFE_PATHS_ENV in safe_path.py)\ntool.run(\"http://localhost:8000/report\")","handlingStrategy":"try-catch","validationCode":"import ipaddress, socket\nfrom urllib.parse import urlparse\n\ndef resolves_to_public_only(url: str) -> bool:\n    host = urlparse(url).hostname\n    if not host:\n        return False\n    try:\n        infos = socket.getaddrinfo(host, None)\n    except socket.gaierror:\n        return False\n    ips = {ipaddress.ip_address(i[4][0]) for i in infos}\n    return all(ip.is_global for ip in ips)","typeGuard":null,"tryCatchPattern":"try:\n    fetch_url_body(url, max_bytes=1_000_000)\nexcept ValueError as e:\n    if \"private/reserved IP\" in str(e):\n        # treat as untrusted content; log and skip rather than bypass\n        logger.warning(\"Blocked internal-network URL: %s\", url)\n        return None\n    raise","preventionTips":["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."],"tags":["ssrf","security","network","url-validation","hardening"],"backgroundTag":null,"analyzedSha":"754d7323beb2fd042e33444a115ea2d5a47193f0","analyzedAt":"2026-08-15T04:06:56.746Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}