lfnovo/open-notebook · critical · ValueError

Hostname ' ' resolves to a link-local address (169.254.x.x)…

Error message

Hostname '{hostname}' resolves to a link-local address (169.254.x.x) which is not allowed for security reasons. These addresses are used for cloud metadata endpoints.

What it means

A user-supplied hostname (not a raw IP) resolved via DNS to a link-local address (169.254.x.x or fe80::/10, including IPv4-mapped IPv6 forms). These ranges host cloud metadata services (AWS/GCP/Azure IMDS), so reaching them via a configurable URL is a classic SSRF vector and is blocked. The message names the hostname because it was resolved, not written literally.

Solutions

  1. Remove the hostname that resolves to 169.254.x.x from the provider config — it will never be allowed
  2. If you genuinely need a cloud API, use its public hostname (e.g. metrics or IMDS via the SDK's built-in mechanism), not a metadata IP
  3. Audit where the DNS record comes from and fix it if it was unintentional
Defensive patterns

Strategy: validation

Validate before calling

import ipaddress

def is_safe_public_target(host: str) -> bool:
    try:
        ip = ipaddress.ip_address(host)
    except ValueError:
        return True  # hostname; DNS-time check still applies
    return not (ip.is_link_local or ip.is_private is False and ip.is_reserved)

Try / catch

try:
    target = await prepare_pinned_http_target(url, provider)
except ValueError as e:
    if "link-local" in str(e) or "metadata" in str(e):
        log_security_event(url)  # potential SSRF attempt
    raise

Prevention

When it happens

Trigger: Setting a provider base_url to a hostname whose DNS resolves to 169.254.169.254 (e.g. a wildcard DNS service like nip.io/sslip.io style names, or an internal name that points at the metadata IP), then running discover_with_config or a connection test.

Common situations: Attempting to probe cloud instance metadata from the app; DNS rebinding-style SSRF tests; misconfigured internal DNS that maps a friendly name to a link-local address.

Related errors


AI-assisted analysis of lfnovo/open-notebook@a7de90d38a (2026-08-27). Data as JSON: /api/errors/676ae4ffa5f9dbab. Report an issue: GitHub.

Appendix: source

Thrown at open_notebook/utils/url_validation.py:226

    return safe


def _reject_dangerous_ip(
    ip: "ipaddress.IPv4Address | ipaddress.IPv6Address",
    hostname: str,
    resolved: bool = False,
) -> None:
    """Raise ValueError if `ip` is a link-local or cloud-metadata address."""
    is_ipv4_mapped_link_local = (
        hasattr(ip, "ipv4_mapped") and ip.ipv4_mapped and ip.ipv4_mapped.is_link_local
    )

    # Block link-local addresses (169.254.x.x / fe80::/10) - used for cloud
    # metadata - including IPv4-mapped IPv6 addresses pointing to link-local
    # (e.g. ::ffff:169.254.169.254 bypasses IPv6 is_link_local check).
    if ip.is_link_local or is_ipv4_mapped_link_local:
        if resolved:
            raise ValueError(
                f"Hostname '{hostname}' resolves to a link-local address (169.254.x.x) "
                "which is not allowed for security reasons. These addresses are used "
                "for cloud metadata endpoints."
            )
        raise ValueError(
            "Link-local addresses (169.254.x.x) are not allowed for security reasons. "
            "These addresses are used for cloud metadata endpoints."
        )

    # Block AWS's IMDSv6 metadata address - a Unique Local Address, not
    # link-local, so it needs its own explicit check. Compare without scope
    # ID so scoped forms (fd00:ec2::254%eth0) cannot bypass the sentinel.
    is_aws_imds_v6 = (
        isinstance(ip, ipaddress.IPv6Address)
        and int(ip) == int(_AWS_IMDS_V6_ADDRESS)
    )
    if is_aws_imds_v6:
        if resolved:

View on GitHub (pinned to a7de90d38a)