{"record":{"id":"1f20ddb6e76dcc22","repo":"xtekky/gpt4free","slug":"invalid-or-unsafe-image-url-image","errorCode":null,"errorMessage":"Invalid or unsafe image url: {image}","messagePattern":"Invalid or unsafe image url: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"g4f/image/copy_images.py","lineNumber":198,"sourceCode":"                media_extension = get_media_extension(image)\n                path = urlparse(image).path\n                if path.startswith(\"/media/\"):\n                    filename = secure_filename(path[len(\"/media/\") :])\n                else:\n                    filename = get_filename(tags, alt, media_extension, image)\n                target_path = os.path.join(dest_dir, filename)\n\n            try:\n                if image.startswith(\"data:\"):\n                    with open(target_path, \"wb\") as f:\n                        f.write(extract_data_uri(image))\n\n                elif (\n                    not os.path.exists(target_path)\n                    or os.lstat(target_path).st_size <= 0\n                ):\n                    if not is_safe_url(image):\n                        raise ValueError(f\"Invalid or unsafe image url: {image}\")\n                    async with session.get(image, ssl=ssl) as response:\n                        response.raise_for_status()\n                        if target is None:\n                            filename = update_filename(response, filename)\n                            target_path = os.path.join(dest_dir, filename)\n                        media_type = response.headers.get(\n                            \"content-type\", \"application/octet-stream\"\n                        )\n                        if media_type not in (\n                            \"application/octet-stream\",\n                            \"binary/octet-stream\",\n                        ):\n                            if media_type not in MEDIA_TYPE_MAP:\n                                raise ValueError(\n                                    f\"Unsupported media type: {media_type}\"\n                                )\n                            if target is None and not media_extension:\n                                media_extension = f\".{MEDIA_TYPE_MAP[media_type]}\"","sourceCodeStart":180,"sourceCodeEnd":216,"githubUrl":"https://github.com/xtekky/gpt4free/blob/973504e1770928ed5fb82f43da528f441ad9ddc3/g4f/image/copy_images.py#L180-L216","documentation":"Thrown inside copy_images.py's copy_image() when an http(s) download is required and is_safe_url(image) returns False. is_safe_url (g4f/image/__init__.py:125) is an SSRF guard: it only allows http/https, rejects backslashes, missing hostnames, and URLs whose hostname resolves to private/loopback/reserved addresses (also cross-checking via urllib3 to catch rebinding tricks). This is a deliberate security block, not a bug.","triggerScenarios":"Passing URLs like http://127.0.0.1/x.png, http://10.0.0.5/a.jpg, http://[::1]/f.webp, http://169.254.169.254/latest/meta-data, a hostname that DNS-resolves to an internal IP, or a non-http scheme such as ftp:// or file://.","commonSituations":"Local development pointing at localhost media servers (blocked by design); cloud metadata endpoint attempts; hostnames that resolve differently inside the deployment network (internal DNS to 192.168.x.x); URLs containing encoded backslashes.","solutions":["Serve the media from a public http(s) host whose DNS resolves to a public IP.","For localhost testing, fetch the bytes yourself and pass a data: URI (copy_image handles 'data:' without the URL check) or place the file directly in the media dir.","Do not attempt to bypass the guard in production — it exists to stop SSRF; if an internal host is genuinely required, extend is_safe_url deliberately with an allowlist."],"exampleFix":"// before\nimages = await copy_images([\"http://192.168.1.10:8080/pic.png\"], ...)\n\n// after\nimport base64\ndata_uri = \"data:image/png;base64,\" + base64.b64encode(open('pic.png','rb').read()).decode()\nimages = await copy_images([data_uri], ...)","handlingStrategy":"validation","validationCode":"from g4f.image import is_safe_url\n\ndef downloadable(image_url: str) -> bool:\n    return (\n        image_url.startswith(\"data:\")\n        or image_url.startswith(\"/\")\n        or is_safe_url(image_url)\n    )","typeGuard":null,"tryCatchPattern":"try:\n    await copy_images([url], prompt=prompt, tags=tags)\nexcept ValueError as e:\n    if \"unsafe image url\" in str(e):\n        logger.warning(\"blocked internal/unsafe url: %s\", url)\n        return []  # skip, do not bypass\n    raise","preventionTips":["Treat this error as an SSRF guard firing correctly — fix the URL, never the check.","For local assets, embed them as data: URIs or write them into the media dir directly.","Resolve/validate hostnames against public IPs before handing URLs to copy_images in server contexts."],"tags":["security","ssrf","url-validation","network"],"backgroundTag":null,"analyzedSha":"973504e1770928ed5fb82f43da528f441ad9ddc3","analyzedAt":"2026-08-14T23:45:32.408Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}