xtekky/gpt4free · error · ValueError

Invalid or unsafe image url: {image}

Error message

Invalid or unsafe image url: {image}

What it means

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.

Source

Thrown at g4f/image/copy_images.py:198

                media_extension = get_media_extension(image)
                path = urlparse(image).path
                if path.startswith("/media/"):
                    filename = secure_filename(path[len("/media/") :])
                else:
                    filename = get_filename(tags, alt, media_extension, image)
                target_path = os.path.join(dest_dir, filename)

            try:
                if image.startswith("data:"):
                    with open(target_path, "wb") as f:
                        f.write(extract_data_uri(image))

                elif (
                    not os.path.exists(target_path)
                    or os.lstat(target_path).st_size <= 0
                ):
                    if not is_safe_url(image):
                        raise ValueError(f"Invalid or unsafe image url: {image}")
                    async with session.get(image, ssl=ssl) as response:
                        response.raise_for_status()
                        if target is None:
                            filename = update_filename(response, filename)
                            target_path = os.path.join(dest_dir, filename)
                        media_type = response.headers.get(
                            "content-type", "application/octet-stream"
                        )
                        if media_type not in (
                            "application/octet-stream",
                            "binary/octet-stream",
                        ):
                            if media_type not in MEDIA_TYPE_MAP:
                                raise ValueError(
                                    f"Unsupported media type: {media_type}"
                                )
                            if target is None and not media_extension:
                                media_extension = f".{MEDIA_TYPE_MAP[media_type]}"

View on GitHub (pinned to 973504e177)

Solutions

  1. Serve the media from a public http(s) host whose DNS resolves to a public IP.
  2. 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.
  3. 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.

Example fix

// before
images = await copy_images(["http://192.168.1.10:8080/pic.png"], ...)

// after
import base64
data_uri = "data:image/png;base64," + base64.b64encode(open('pic.png','rb').read()).decode()
images = await copy_images([data_uri], ...)
Defensive patterns

Strategy: validation

Validate before calling

from g4f.image import is_safe_url

def downloadable(image_url: str) -> bool:
    return (
        image_url.startswith("data:")
        or image_url.startswith("/")
        or is_safe_url(image_url)
    )

Try / catch

try:
    await copy_images([url], prompt=prompt, tags=tags)
except ValueError as e:
    if "unsafe image url" in str(e):
        logger.warning("blocked internal/unsafe url: %s", url)
        return []  # skip, do not bypass
    raise

Prevention

When it happens

Trigger: 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://.

Common situations: 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.

Related errors


AI-assisted analysis of xtekky/gpt4free@973504e177 (2026-08-14). Data as JSON: /api/errors/1f20ddb6e76dcc22. Report an issue: GitHub.