fix(agent): LLM traffic honours CIDR and wildcard NO_PROXY entries like the platform adapters do

Three answers to "is this host in NO_PROXY": process_bootstrap used the stdlib
proxy_bypass_environment (no CIDR, no `*.`), gateway/platforms/base.py had a
full matcher (should_bypass_proxy) and a second suffix-only one
(is_host_excluded_by_no_proxy, used by Slack). Live-verified: with
NO_PROXY=10.0.0.0/8 Telegram bypassed the proxy while the LLM call to a 10.x
endpoint went through it.

The full matcher moves to the leaf module agent/proxy_bypass.py (stdlib only,
importable at early boot); both base.py functions are one-line forwarders and
process_bootstrap._get_proxy_for_base_url uses it (passing host:port so
port-qualified entries match). The six-key proxy env scan is also shared.
This commit is contained in:
teknium1
2026-09-12 19:42:53 -07:00
committed by Teknium
parent 1512edfb85
commit 164c5ea142
4 changed files with 18 additions and 88 deletions
+8 -8
View File
@@ -15,10 +15,10 @@ import socket
import sys
import threading
import time
import urllib.request
from typing import Any, Optional
from utils import base_url_hostname, normalize_proxy_url
from agent.proxy_bypass import first_proxy_env_value, should_bypass_proxy
_OPENAI_CLS_CACHE = None
@@ -288,18 +288,18 @@ class _SafeWriter:
def _get_proxy_from_env() -> Optional[str]:
"""First configured proxy URL from HTTPS_PROXY / HTTP_PROXY / ALL_PROXY (any case), or None."""
keys = ("HTTPS_PROXY", "HTTP_PROXY", "ALL_PROXY", "https_proxy", "http_proxy", "all_proxy")
return next((normalize_proxy_url(v) for k in keys if (v := os.environ.get(k, "").strip())), None)
value = first_proxy_env_value()
return normalize_proxy_url(value) if value else None
def _get_proxy_for_base_url(base_url: Optional[str]) -> Optional[str]:
"""Env-configured proxy unless NO_PROXY excludes this base URL."""
"""Env-configured proxy unless NO_PROXY excludes this base URL (same matcher as the
gateway adapters: CIDR, ``*.`` wildcards and host:port entries all count)."""
proxy = _get_proxy_from_env()
host = base_url_hostname(base_url) if proxy and base_url else ""
try:
return None if host and urllib.request.proxy_bypass_environment(host) else proxy
except Exception:
if not (proxy and base_url):
return proxy
raw = base_url.strip()
return None if should_bypass_proxy(raw if "://" in raw else f"//{raw}") else proxy
def _shared_transport_cls():
+3 -2
View File
@@ -26,11 +26,12 @@ def first_proxy_env_value() -> str:
def split_host_port(value: str) -> tuple[str, int | None]:
"""``(host, port)`` from a URL, ``[v6]:port``, ``host:port`` or bare host; host lowercased."""
"""``(host, port)`` from a URL (scheme optional: ``//host/path``), ``[v6]:port``,
``host:port`` or bare host; host lowercased."""
raw = str(value or "").strip()
if not raw:
return "", None
if "://" in raw:
if "://" in raw or raw.startswith("//"):
parsed = urlsplit(raw)
host, port = parsed.hostname or "", parsed.port
elif raw.startswith("[") and "]" in raw:
+1 -1
View File
@@ -67,7 +67,7 @@ def parse_retry_after_seconds(value_or_headers: Any) -> Optional[float]:
# Free-text "reset" grammars providers put in error bodies, tried in order. One table so the
# conversation loop's error context and the credential pool's cooldown agree on the same wait.
_QUOTA_RESET_DELAY_RE = re.compile(r"quotaResetDelay[:\s\"]+(\d+(?:\.\d+)?)(ms|s)", re.IGNORECASE)
# "Resets in 4hr 5min" (OpenCode Go weekly limits), "resets in 2 hours 5 minutes", "resets in 30s".
# "Resets in 4hr 5min" (weekly usage limits), "resets in 2 hours 5 minutes", "resets in 30s".
_RESETS_IN_RE = re.compile(
r"resets?\s+in\s+"
r"(?:(\d+(?:\.\d+)?)\s*(?:h|hr|hrs|hour|hours)\b\s*)?"
+6 -77
View File
@@ -20,6 +20,7 @@ from abc import ABC, abstractmethod
from urllib.parse import urlsplit
from utils import normalize_proxy_url
from agent.proxy_bypass import first_proxy_env_value, should_bypass_proxy as _should_bypass_proxy
logger = logging.getLogger(__name__)
@@ -264,69 +265,10 @@ def _detect_macos_system_proxy() -> str | None:
return None
def _split_host_port(value: str) -> tuple[str, int | None]:
"""``(host, port)`` from a URL, ``[v6]:port``, ``host:port`` or bare host; host lowercased."""
raw = str(value or "").strip()
if not raw:
return "", None
if "://" in raw:
parsed = urlsplit(raw)
host, port = parsed.hostname or "", parsed.port
elif raw.startswith("[") and "]" in raw:
host, _, rest = raw[1:].partition("]")
port = int(rest[1:]) if rest.startswith(":") and rest[1:].isdigit() else None
elif raw.count(":") == 1 and raw.rpartition(":")[2].isdigit():
host, _, port_s = raw.rpartition(":")
port = int(port_s)
else:
host, port = raw.strip("[]"), None
return host.lower().rstrip("."), port
def _no_proxy_entries() -> list[str]:
return [
part.strip() for key in ("NO_PROXY", "no_proxy")
for part in os.environ.get(key, "").split(",") if part.strip()]
def _ip_or_none(value: str, parse=ipaddress.ip_address):
"""``parse(value)`` or None on ``ValueError`` (``parse`` is ip_address / ip_network)."""
try:
return parse(value)
except ValueError:
return None
def _no_proxy_entry_matches(entry: str, host: str, port: int | None = None) -> bool:
token = str(entry or "").strip().lower()
if not token:
return False
if token == "*":
return True
token_host, token_port = _split_host_port(token)
if not token_host or (token_port is not None and (port is None or token_port != port)):
return False
host_ip = _ip_or_none(host)
network = _ip_or_none(token_host, lambda v: ipaddress.ip_network(v, strict=False))
if network is not None: # CIDR or bare IP literal (a /32 / /128 network)
return host_ip is not None and host_ip in network
if token_host.startswith("*."):
return host.endswith(token_host[1:])
if token_host.startswith("."):
return host == token_host[1:] or host.endswith(token_host)
return host == token_host or host.endswith(f".{token_host}")
def should_bypass_proxy(target_hosts: str | list[str] | tuple[str, ...] | set[str] | None) -> bool:
"""True when NO_PROXY/no_proxy matches at least one target host (exact hosts, domain /
wildcard suffixes, IP literals, CIDR ranges, optional host:port entries, ``*``)."""
entries = _no_proxy_entries()
if not entries or not target_hosts:
return False
candidates = [target_hosts] if isinstance(target_hosts, str) else list(target_hosts)
return any(
host and any(_no_proxy_entry_matches(entry, host, port) for entry in entries)
for host, port in map(_split_host_port, map(str, candidates)))
return _should_bypass_proxy(target_hosts)
def resolve_proxy_url(
@@ -347,8 +289,7 @@ def resolve_proxy_url(
if not value:
if not gateway_trust_env(): # only the explicit per-platform var is honored
return None
keys = ("HTTPS_PROXY", "HTTP_PROXY", "ALL_PROXY", "https_proxy", "http_proxy", "all_proxy")
value = next((v for k in keys if (v := (os.environ.get(k) or "").strip())), "")
value = first_proxy_env_value()
proxy = normalize_proxy_url(value or _detect_macos_system_proxy())
return None if proxy and should_bypass_proxy(target_hosts) else proxy
@@ -412,21 +353,9 @@ def proxy_kwargs_for_aiohttp(proxy_url: str | None) -> tuple[dict, dict]:
def is_host_excluded_by_no_proxy(hostname: str, no_proxy_value: str | None = None) -> bool:
"""Return True when ``hostname`` matches a ``NO_PROXY`` entry (comma/whitespace
separated; leading-dot and ``*.`` entries match the apex domain and subdomains)."""
if no_proxy_value is None:
no_proxy_value = os.environ.get("NO_PROXY") or os.environ.get("no_proxy") or ""
lower_hostname = hostname.lower()
for entry in re.split(r"[\s,]+", no_proxy_value.strip()):
normalized = entry.strip().lower()
if not normalized:
continue
if normalized == "*":
return True
normalized = normalized[2:] if normalized.startswith("*.") else normalized.removeprefix(".")
if lower_hostname == normalized or lower_hostname.endswith(f".{normalized}"):
return True
return False
"""Return True when ``hostname`` matches a ``NO_PROXY`` entry (``no_proxy_value`` overrides the
environment); same matcher as :func:`should_bypass_proxy`."""
return _should_bypass_proxy(hostname, no_proxy_value=no_proxy_value)
import dataclasses