""" Network policy — Egress control for container mode. Default-deny: only declared LLM backend domains and explicitly allowlisted domains can be reached. In development mode, no restrictions apply. """ from __future__ import annotations import logging from typing import Optional from urllib.parse import urlparse logger = logging.getLogger(__name__) # Module state _egress_enabled: bool = False _allowed_domains: set[str] = set() # LLM provider domains that are auto-allowlisted based on configured backends LLM_PROVIDER_DOMAINS: dict[str, list[str]] = { "xai": ["api.x.ai"], "anthropic": ["api.anthropic.com"], "openai": ["api.openai.com"], "ollama": ["localhost", "127.0.0.1"], } def enable_egress_control() -> None: """Enable egress control (deny by default).""" global _egress_enabled _egress_enabled = True def disable_egress_control() -> None: """Disable egress control (allow all).""" global _egress_enabled _egress_enabled = False def is_egress_controlled() -> bool: """Check if egress control is active.""" return _egress_enabled def allow_domain(domain: str) -> None: """Add a domain to the egress allowlist.""" _allowed_domains.add(domain.lower()) def allow_domains(domains: list[str]) -> None: """Add multiple domains to the egress allowlist.""" for domain in domains: _allowed_domains.add(domain.lower()) def allow_llm_provider(provider: str) -> None: """Auto-allowlist domains for an LLM provider.""" domains = LLM_PROVIDER_DOMAINS.get(provider, []) for domain in domains: _allowed_domains.add(domain.lower()) logger.debug(f"Auto-allowlisted domain for {provider}: {domain}") def check_egress(url: str) -> Optional[str]: """ Check if a URL is allowed by the egress policy. Returns None if allowed, or an error message if blocked. """ if not _egress_enabled: return None try: parsed = urlparse(url) hostname = (parsed.hostname or "").lower() except Exception: return "Invalid URL" if not hostname: return "URL must have a host" # Check exact domain match if hostname in _allowed_domains: return None # Check wildcard subdomain match (e.g., *.example.com) parts = hostname.split(".") for i in range(1, len(parts)): parent = ".".join(parts[i:]) if f"*.{parent}" in _allowed_domains or parent in _allowed_domains: return None logger.warning(f"Egress blocked: {hostname} not in allowlist") return f"Egress blocked: domain '{hostname}' is not in the allowed domains list" def get_allowed_domains() -> set[str]: """Get the current set of allowed domains.""" return set(_allowed_domains) def reset() -> None: """Reset network policy state (for testing).""" global _egress_enabled _egress_enabled = False _allowed_domains.clear()