{"record":{"id":"24424933f155630b","repo":"crewAIInc/crewAI","slug":"url-scheme-parsed-scheme-is-not-allowed-only","errorCode":null,"errorMessage":"URL scheme '{parsed.scheme}' is not allowed. Only http and https are supported.","messagePattern":"URL scheme '(.+?)' is not allowed\\. Only http and https are supported\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"lib/crewai-tools/src/crewai_tools/security/safe_path.py","lineNumber":219,"sourceCode":"        logger.warning(\n            \"%s is enabled — skipping URL validation for: %s\",\n            _UNSAFE_PATHS_ENV,\n            url,\n        )\n        return url\n\n    parsed = urlparse(url)\n\n    # Block file:// scheme\n    if parsed.scheme == \"file\":\n        raise ValueError(\n            f\"file:// URLs are not allowed: '{url}'. \"\n            f\"Use a file path instead, or set {_UNSAFE_PATHS_ENV}=true to bypass.\"\n        )\n\n    # Only allow http and https\n    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}. \"","sourceCodeStart":201,"sourceCodeEnd":237,"githubUrl":"https://github.com/crewAIInc/crewAI/blob/754d7323beb2fd042e33444a115ea2d5a47193f0/lib/crewai-tools/src/crewai_tools/security/safe_path.py#L201-L237","documentation":"Raised by the same URL validator for any scheme that is not http or https (file:// gets its own dedicated error first). ftp://, ws://, gopher://, javascript:, data:, or scheme-less strings that urlparse parses with an empty scheme all fail here. The validator's purpose is SSRF reduction: only web schemes proceed to DNS resolution and private-IP blocking.","triggerScenarios":"Passing ftp://example.com/file, ws://host/socket, or data:text/html,... to a URL-accepting loader/tool; passing a bare hostname like 'example.com/path' (urlparse yields scheme ''); URIs from config or LLM output using non-web schemes.","commonSituations":"Config values meant to be web URLs accidentally holding other protocol DSNs; mixed input lists where some entries are file paths or e-mail-style URIs (mailto:); penetration tests probing scheme handling; users pasting FTP download links.","solutions":["Use full http:// or https:// URLs including the scheme.","Normalize bare hostnames before validation: if '://' not in url: url = 'https://' + url.","Route non-web resources to the right loader: ftp/file resources to a path-based loader after downloading, not the URL validator.","Validate scheme on your own input boundary so your error messages are domain-specific."],"exampleFix":"# before\nvalidate_url(\"example.com/docs\")     # empty scheme -> ValueError\nvalidate_url(\"ftp://example.com/f\")    # ftp -> ValueError\n\n# after\ndef normalize(url: str) -> str:\n    return url if \"://\" in url else f\"https://{url}\"\n\nvalidate_url(normalize(\"example.com/docs\"))  # ok","handlingStrategy":"validation","validationCode":"from urllib.parse import urlparse\n\ndef normalize_web_url(url: str) -> str | None:\n    if \"://\" not in url:\n        url = \"https://\" + url\n    return url if urlparse(url).scheme in (\"http\", \"https\") else None","typeGuard":null,"tryCatchPattern":"try:\n    validated = validate_url(candidate)\nexcept ValueError as e:\n    if \"URL scheme\" in str(e):\n        candidate = normalize_web_url(candidate)\n        validated = validate_url(candidate) if candidate else reject(candidate)\n    else:\n        raise","preventionTips":["Always emit fully-qualified https:// URLs from your own code and config.","Auto-prepend a scheme to bare hostnames before validation.","Reject non-web schemes early with a clear domain-specific error message."],"tags":["security","url-validation","ssrf","input-validation"],"backgroundTag":null,"analyzedSha":"754d7323beb2fd042e33444a115ea2d5a47193f0","analyzedAt":"2026-08-15T04:06:56.746Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}