{"record":{"id":"8c7f9f9e8b5d5f38","repo":"langchain-ai/langchain","slug":"kubernetes-internal-dns","errorCode":null,"errorMessage":"Kubernetes internal DNS","messagePattern":"Kubernetes internal DNS","errorType":"exception","errorClass":"SSRFBlockedError","httpStatus":null,"severity":"error","filePath":"libs/core/langchain_core/_security/_policy.py","lineNumber":232,"sourceCode":"\ndef validate_hostname(hostname: str, policy: SSRFPolicy) -> None:\n    \"\"\"Validate a hostname against the SSRF policy.\n\n    Raises SSRFBlockedError if the hostname is blocked.\n    \"\"\"\n    lower = hostname.lower()\n\n    if policy.block_localhost and lower in _LOCALHOST_NAMES:\n        msg = \"localhost address\"\n        raise SSRFBlockedError(msg)\n\n    if policy.block_cloud_metadata and lower in _CLOUD_METADATA_HOSTNAMES:\n        msg = \"cloud metadata endpoint\"\n        raise SSRFBlockedError(msg)\n\n    if policy.block_k8s_internal and lower.endswith(_K8S_SUFFIX):\n        msg = \"Kubernetes internal DNS\"\n        raise SSRFBlockedError(msg)\n\n\ndef _effective_allowed_hosts(policy: SSRFPolicy) -> frozenset[str]:\n    \"\"\"Return allowed_hosts, augmented for local environments.\"\"\"\n    extra: set[str] = set()\n    if os.environ.get(\"LANGCHAIN_ENV\", \"\").startswith(\"local\"):\n        extra.update({\"localhost\", \"testserver\"})\n    if extra:\n        return policy.allowed_hosts | frozenset(extra)\n    return policy.allowed_hosts\n\n\nasync def validate_url(url: str, policy: SSRFPolicy = DEFAULT_SSRF_POLICY) -> None:\n    \"\"\"Validate a URL against the SSRF policy, including DNS resolution.\n\n    This is the primary entry-point for async code paths. It delegates\n    scheme/hostname/allowed-hosts checks to `validate_url_sync`, then\n    resolves DNS and validates every resolved IP.","sourceCodeStart":214,"sourceCodeEnd":250,"githubUrl":"https://github.com/langchain-ai/langchain/blob/e32fa9a52eab3b61ad7a45399bfde59b3e580fc4/libs/core/langchain_core/_security/_policy.py#L214-L250","documentation":"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.","triggerScenarios":"`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.","commonSituations":"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.","solutions":["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).","Prefer in-cluster service calls over the SSRF-guarded fetch path — call the service client directly rather than through URL validation.","Namespace the relaxation: keep the default policy for external/user URLs and a separate permissive policy for known-internal base URLs."],"exampleFix":"# before\nvalidate_url_sync('http://embeddings.svc:8080/embed', DEFAULT_SSRF_POLICY)\n# SSRFBlockedError: Kubernetes internal DNS\n\n# after\ninternal_policy = SSRFPolicy(allowed_hosts={'embeddings.default.svc'})\nvalidate_url_sync('http://embeddings.svc:8080/embed', internal_policy)","handlingStrategy":"try-catch","validationCode":"def is_k8s_svc_host(url: str) -> bool:\n    host = (urlparse(url).hostname or \"\").lower()\n    return host.endswith(\".svc\") or host.endswith(\".svc.cluster.local\")","typeGuard":null,"tryCatchPattern":"from langchain_core._security._policy import SSRFBlockedError\n\ntry:\n    validate_url_sync(url, policy)\nexcept SSRFBlockedError as e:\n    if \"Kubernetes\" in str(e) and url_host in settings.trusted_internal_hosts:\n        return validate_url_sync(url, internal_policy)\n    raise","preventionTips":["Keep two policies: strict defaults for external/user URLs, a permissive one scoped to configured internal service hosts.","Call in-cluster services via their client library instead of the SSRF-guarded URL fetch when possible."],"tags":["ssrf","security","kubernetes","dns"],"backgroundTag":null,"analyzedSha":"e32fa9a52eab3b61ad7a45399bfde59b3e580fc4","analyzedAt":"2026-08-14T18:42:09.092Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}