From 09382bbbecd8968426a097c236bfbbc7fca1a041 Mon Sep 17 00:00:00 2001 From: Palash Debnath <4178343+debpalash@users.noreply.github.com> Date: Mon, 28 Sep 2026 17:17:33 -0700 Subject: [PATCH] Keep SDK loopback requests direct and disable shared cookies --- backend/services/llm_transport.py | 16 +++++++++++++++- docs/electron-llm-providers.md | 2 ++ tests/test_llm_transports.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/backend/services/llm_transport.py b/backend/services/llm_transport.py index b735c28d0..43f8ac8b7 100644 --- a/backend/services/llm_transport.py +++ b/backend/services/llm_transport.py @@ -4,9 +4,18 @@ from __future__ import annotations import os import atexit from functools import lru_cache +from http.cookiejar import CookieJar, DefaultCookiePolicy from types import SimpleNamespace +class _NoCookies(DefaultCookiePolicy): + def set_ok(self, cookie, request): + return False + + def return_ok(self, cookie, request): + return False + + def _check_sdk_request(request): """Validate the effective URL after SDK environment/provider resolution.""" from ipaddress import ip_address @@ -28,7 +37,12 @@ def _sdk_http_client(): # A shared thread-safe pool also keeps streaming responses alive after # completion() returns. It carries no provider credentials of its own. import httpx - client = httpx.Client(follow_redirects=False, event_hooks={"request": [_check_sdk_request]}) + client = httpx.Client( + follow_redirects=False, + trust_env=False, + cookies=CookieJar(policy=_NoCookies()), + event_hooks={"request": [_check_sdk_request]}, + ) atexit.register(client.close) return client diff --git a/docs/electron-llm-providers.md b/docs/electron-llm-providers.md index c6817bcc3..e4960b9d1 100644 --- a/docs/electron-llm-providers.md +++ b/docs/electron-llm-providers.md @@ -150,3 +150,5 @@ LM Studio automatic discovery accepts only loaded `llm`/`vlm` entries from its n SDK transport validates the final request URL, including environment-derived OpenAI endpoints, and disables redirects for OpenAI-compatible and native Anthropic, Bedrock, and Vertex requests. +The shared HTTP client ignores environment proxies so loopback prompts stay local, +and never stores response cookies between provider requests. diff --git a/tests/test_llm_transports.py b/tests/test_llm_transports.py index 92d889408..963990a9b 100644 --- a/tests/test_llm_transports.py +++ b/tests/test_llm_transports.py @@ -159,3 +159,33 @@ def test_anthropic_does_not_follow_credentialed_redirect(registry): assert seen == ["/v1/messages"] finally: server.shutdown(); server.server_close(); thread.join(timeout=2) + + +@pytest.mark.parametrize("proxy_enabled", [False, True]) +def test_sdk_pool_keeps_loopback_direct_and_does_not_persist_cookies(monkeypatch, proxy_enabled): + from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer + from threading import Thread + from services.llm_transport import _sdk_http_client + seen = [] + class Handler(BaseHTTPRequestHandler): + def log_message(self, *args): pass + def do_GET(self): + seen.append(self.headers.get("Cookie")) + self.send_response(200) + self.send_header("Set-Cookie", "session=private; Path=/") + self.send_header("Content-Length", "0") + self.end_headers() + for key in ("HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY", "http_proxy", "https_proxy", "all_proxy"): + monkeypatch.setenv(key, "http://127.0.0.1:1" if proxy_enabled else "") + monkeypatch.setenv("NO_PROXY", "") + monkeypatch.setenv("no_proxy", "") + server = ThreadingHTTPServer(("127.0.0.1", 0), Handler) + thread = Thread(target=server.serve_forever, daemon=True); thread.start() + try: + with _sdk_http_client.__wrapped__() as client: + for _ in range(2): + assert client.get(f"http://127.0.0.1:{server.server_port}/", timeout=2).status_code == 200 + assert not list(client.cookies.jar) + assert seen == [None, None] + finally: + server.shutdown(); server.server_close(); thread.join(timeout=2)