libs/core/langchain_core/_security/_ssrf_protection.py PYTHON 156 lines View on github.com → Search inside
1"""SSRF Protection - thin wrapper raising ValueError for internal callers.23Delegates all validation to `langchain_core._security._policy`.4"""56import os7import socket8from typing import Annotated, Any9from urllib.parse import urlparse1011from pydantic import (12    AnyHttpUrl,13    BeforeValidator,14    HttpUrl,15)1617from langchain_core._security._exceptions import SSRFBlockedError18from langchain_core._security._policy import (19    SSRFPolicy,20)21from langchain_core._security._policy import (22    validate_resolved_ip as _validate_resolved_ip,23)24from langchain_core._security._policy import (25    validate_url_sync as _validate_url_sync,26)272829def _policy_for(*, allow_private: bool, allow_http: bool) -> SSRFPolicy:30    """Build an `SSRFPolicy` from the legacy flag interface."""31    schemes = frozenset({"http", "https"}) if allow_http else frozenset({"https"})32    return SSRFPolicy(33        allowed_schemes=schemes,34        block_private_ips=not allow_private,35        block_localhost=not allow_private,36        block_cloud_metadata=True,37        block_k8s_internal=True,38    )394041def validate_safe_url(42    url: str | AnyHttpUrl,43    *,44    allow_private: bool = False,45    allow_http: bool = True,46) -> str:47    """Validate a URL for SSRF protection.4849    This function validates URLs to prevent Server-Side Request Forgery (SSRF) attacks50    by blocking requests to private networks and cloud metadata endpoints.5152    Args:53        url: The URL to validate (string or Pydantic HttpUrl).54        allow_private: If `True`, allows private IPs and localhost (for development).55                      Cloud metadata endpoints are ALWAYS blocked.56        allow_http: If `True`, allows both HTTP and HTTPS.  If `False`, only HTTPS.5758    Returns:59        The validated URL as a string.6061    Raises:62        ValueError: If URL is invalid or potentially dangerous.63    """64    url_str = str(url)65    parsed = urlparse(url_str)66    hostname = parsed.hostname or ""6768    # Test-environment bypass (preserved from original implementation)69    if (70        os.environ.get("LANGCHAIN_ENV") == "local_test"71        and hostname.startswith("test")72        and "server" in hostname73    ):74        return url_str7576    policy = _policy_for(allow_private=allow_private, allow_http=allow_http)7778    # Synchronous scheme + hostname checks79    try:80        _validate_url_sync(url_str, policy)81    except SSRFBlockedError as exc:82        raise ValueError(str(exc)) from exc8384    # DNS resolution and IP validation85    try:86        addr_info = socket.getaddrinfo(87            hostname,88            parsed.port or (443 if parsed.scheme == "https" else 80),89            socket.AF_UNSPEC,90            socket.SOCK_STREAM,91        )9293        for result in addr_info:94            ip_str: str = result[4][0]  # type: ignore[assignment]95            try:96                _validate_resolved_ip(ip_str, policy)97            except SSRFBlockedError as exc:98                raise ValueError(str(exc)) from exc99100    except socket.gaierror as e:101        msg = f"Failed to resolve hostname '{hostname}': {e}"102        raise ValueError(msg) from e103    except OSError as e:104        msg = f"Network error while validating URL: {e}"105        raise ValueError(msg) from e106107    return url_str108109110def is_safe_url(111    url: str | AnyHttpUrl,112    *,113    allow_private: bool = False,114    allow_http: bool = True,115) -> bool:116    """Non-throwing version of `validate_safe_url`."""117    try:118        validate_safe_url(url, allow_private=allow_private, allow_http=allow_http)119    except ValueError:120        return False121    else:122        return True123124125def _validate_url_ssrf_strict(v: Any) -> Any:126    """Validate URL for SSRF protection (strict mode)."""127    if isinstance(v, str):128        validate_safe_url(v, allow_private=False, allow_http=True)129    return v130131132def _validate_url_ssrf_https_only(v: Any) -> Any:133    if isinstance(v, str):134        validate_safe_url(v, allow_private=False, allow_http=False)135    return v136137138def _validate_url_ssrf_relaxed(v: Any) -> Any:139    """Validate URL for SSRF protection (relaxed mode - allows private IPs)."""140    if isinstance(v, str):141        validate_safe_url(v, allow_private=True, allow_http=True)142    return v143144145# Annotated types with SSRF protection146SSRFProtectedUrl = Annotated[HttpUrl, BeforeValidator(_validate_url_ssrf_strict)]147SSRFProtectedUrlRelaxed = Annotated[148    HttpUrl, BeforeValidator(_validate_url_ssrf_relaxed)149]150SSRFProtectedHttpsUrl = Annotated[151    HttpUrl, BeforeValidator(_validate_url_ssrf_https_only)152]153SSRFProtectedHttpsUrlStr = Annotated[154    str, BeforeValidator(_validate_url_ssrf_https_only)155]

Code quality findings 5

Ensure functions have docstrings for documentation
missing-docstring
def validate_safe_url(
Ensure functions have docstrings for documentation
missing-docstring
def is_safe_url(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(v, str):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(v, str):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(v, str):

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.