langchain-ai/langchain · error · SSRFBlockedError

Kubernetes internal DNS

Error message

Kubernetes internal DNS

What it means

Raised by `validate_hostname` when the hostname ends with the Kubernetes-internal DNS suffix (`_K8S_SUFFIX`, i.e. `.svc`, covering `*.default.svc`, `*.kube-system.svc` cluster-local service names) and `policy.block_k8s_internal` is enabled. In-cluster service DNS is reachable only from inside the cluster and often unauthenticated, so SSRF policy blocks it like any other internal range.

Source

Thrown at libs/core/langchain_core/_security/_policy.py:232

def validate_hostname(hostname: str, policy: SSRFPolicy) -> None:
    """Validate a hostname against the SSRF policy.

    Raises SSRFBlockedError if the hostname is blocked.
    """
    lower = hostname.lower()

    if policy.block_localhost and lower in _LOCALHOST_NAMES:
        msg = "localhost address"
        raise SSRFBlockedError(msg)

    if policy.block_cloud_metadata and lower in _CLOUD_METADATA_HOSTNAMES:
        msg = "cloud metadata endpoint"
        raise SSRFBlockedError(msg)

    if policy.block_k8s_internal and lower.endswith(_K8S_SUFFIX):
        msg = "Kubernetes internal DNS"
        raise SSRFBlockedError(msg)


def _effective_allowed_hosts(policy: SSRFPolicy) -> frozenset[str]:
    """Return allowed_hosts, augmented for local environments."""
    extra: set[str] = set()
    if os.environ.get("LANGCHAIN_ENV", "").startswith("local"):
        extra.update({"localhost", "testserver"})
    if extra:
        return policy.allowed_hosts | frozenset(extra)
    return policy.allowed_hosts


async def validate_url(url: str, policy: SSRFPolicy = DEFAULT_SSRF_POLICY) -> None:
    """Validate a URL against the SSRF policy, including DNS resolution.

    This is the primary entry-point for async code paths. It delegates
    scheme/hostname/allowed-hosts checks to `validate_url_sync`, then
    resolves DNS and validates every resolved IP.

View on GitHub (pinned to e32fa9a52e)

Solutions

  1. If the in-cluster target is trusted, construct the policy with that service hostname in `allowed_hosts` (or set `block_k8s_internal=False` only for that internal client, never for user-supplied URLs).
  2. Prefer in-cluster service calls over the SSRF-guarded fetch path — call the service client directly rather than through URL validation.
  3. Namespace the relaxation: keep the default policy for external/user URLs and a separate permissive policy for known-internal base URLs.

Example fix

# before
validate_url_sync('http://embeddings.svc:8080/embed', DEFAULT_SSRF_POLICY)
# SSRFBlockedError: Kubernetes internal DNS

# after
internal_policy = SSRFPolicy(allowed_hosts={'embeddings.default.svc'})
validate_url_sync('http://embeddings.svc:8080/embed', internal_policy)
Defensive patterns

Strategy: try-catch

Validate before calling

def is_k8s_svc_host(url: str) -> bool:
    host = (urlparse(url).hostname or "").lower()
    return host.endswith(".svc") or host.endswith(".svc.cluster.local")

Try / catch

from langchain_core._security._policy import SSRFBlockedError

try:
    validate_url_sync(url, policy)
except SSRFBlockedError as e:
    if "Kubernetes" in str(e) and url_host in settings.trusted_internal_hosts:
        return validate_url_sync(url, internal_policy)
    raise

Prevention

When it happens

Trigger: `validate_hostname('prometheus.monitoring.svc', policy)`, `validate_url_sync('http://my-service.default.svc:8080/api')`, or any fetched URL whose host ends in `.svc` under the default `block_k8s_internal=True`. A crafted name like `evil-attacker.example.svc.attacker.com` is not caught (only `endswith` on the exact suffix is checked) but ordinary cluster names are.

Common situations: Deploying a langchain service inside Kubernetes that legitimately needs to call another in-cluster service (internal embeddings API, feature store) through a URL-validated fetch; Helm-chart env vars commonly use `<service>.<namespace>.svc` addresses and suddenly fail SSRF validation after the guard ships.

Related errors


AI-assisted analysis of langchain-ai/langchain@e32fa9a52e (2026-08-14). Data as JSON: /api/errors/8c7f9f9e8b5d5f38. Report an issue: GitHub.