Coverage for src/local_deep_research/security/egress/fetch.py: 88%
12 statements
« prev ^ index » next coverage.py v7.15.1, created at 2026-07-19 23:35 +0000
« prev ^ index » next coverage.py v7.15.1, created at 2026-07-19 23:35 +0000
1"""Egress-aware URL fetch validation.
3A thin wrapper over the general SSRF validator that threads the egress
4*scope* through, so this layer (egress) depends on the SSRF validator rather
5than the SSRF validator depending on egress.
6"""
8from loguru import logger
10from ..ssrf_validator import validate_url
11from .policy import EgressScope
14def policy_aware_validate_url(url: str, egress_context=None) -> bool:
15 """Validate ``url`` for SSRF, taking the egress policy scope into account.
17 Under ``EgressScope.PRIVATE_ONLY`` the policy lets the user reach
18 private hosts (local lab deployments — Ollama on 127.0.0.1, SearXNG
19 on 192.168.x). Plain ``validate_url(url)`` would reject those at
20 the SSRF layer even though policy explicitly permits them. This
21 wrapper threads the scope through so a PRIVATE_ONLY run can reach
22 private hosts WITHOUT requiring the operator to set
23 ``SSRF_ALLOW_PRIVATE_IPS=1`` globally.
25 Cloud-metadata IPs in ``ALWAYS_BLOCKED_METADATA_IPS`` remain blocked
26 regardless of scope (handled inside ``is_ip_blocked``).
28 When ``egress_context`` is ``None`` the call is equivalent to
29 ``validate_url(url)`` (strict defaults).
30 """
31 if egress_context is None:
32 return validate_url(url)
33 try:
34 if egress_context.scope == EgressScope.PRIVATE_ONLY:
35 return validate_url(url, allow_private_ips=True)
36 except Exception: # noqa: silent-exception
37 # Defensive: any attribute error here means the context is
38 # malformed; treat as if no policy was supplied and fall back to
39 # strict defaults. Logging is intentionally suppressed to avoid
40 # leaking policy state via timing.
41 logger.debug("policy_aware_validate_url defensive fallback")
42 return validate_url(url)