From e1663abe4a5456e80bab3bc3384a17ef376acd3a Mon Sep 17 00:00:00 2001 From: Hermes Bot Date: Sun, 24 May 2026 04:03:14 +0000 Subject: [PATCH] fix: sanitize NO_PROXY env vars containing newline characters Newlines in NO_PROXY/no_proxy (common in Docker/.env files) cause httpx.InvalidURL when the client is initialized. Strip and rejoin entries before httpx reads the environment. Co-Authored-By: Claude Sonnet 4.6 --- src/openai/_base_client.py | 10 ++++++++++ tests/test_client.py | 24 ++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/openai/_base_client.py b/src/openai/_base_client.py index 216b36aabd..7b3f55f08a 100644 --- a/src/openai/_base_client.py +++ b/src/openai/_base_client.py @@ -1,5 +1,6 @@ from __future__ import annotations +import os import sys import json import time @@ -831,11 +832,19 @@ def _idempotency_key(self) -> str: return f"stainless-python-retry-{uuid.uuid4()}" +def _sanitize_proxy_env_vars() -> None: + for key in ("NO_PROXY", "no_proxy"): + val = os.environ.get(key) + if val and "\n" in val: + os.environ[key] = ",".join(part.strip() for part in val.replace("\n", ",").split(",") if part.strip()) + + class _DefaultHttpxClient(httpx.Client): def __init__(self, **kwargs: Any) -> None: kwargs.setdefault("timeout", DEFAULT_TIMEOUT) kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS) kwargs.setdefault("follow_redirects", True) + _sanitize_proxy_env_vars() super().__init__(**kwargs) @@ -1423,6 +1432,7 @@ def __init__(self, **kwargs: Any) -> None: kwargs.setdefault("timeout", DEFAULT_TIMEOUT) kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS) kwargs.setdefault("follow_redirects", True) + _sanitize_proxy_env_vars() super().__init__(**kwargs) diff --git a/tests/test_client.py b/tests/test_client.py index 2d8955a58e..56374f72f5 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1292,6 +1292,18 @@ def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> N assert len(mounts) == 1 assert mounts[0][0].pattern == "https://" + def test_no_proxy_with_newlines(self, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("NO_PROXY", "localhost\n127.0.0.1\n") + monkeypatch.delenv("no_proxy", raising=False) + client = DefaultHttpxClient() + assert client is not None + + def test_no_proxy_lowercase_with_newlines(self, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("no_proxy", "localhost\n127.0.0.1\n") + monkeypatch.delenv("NO_PROXY", raising=False) + client = DefaultHttpxClient() + assert client is not None + @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning") def test_default_client_creation(self) -> None: # Ensure that the client can be initialized without any exceptions @@ -2552,6 +2564,18 @@ async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch assert len(mounts) == 1 assert mounts[0][0].pattern == "https://" + async def test_no_proxy_with_newlines(self, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("NO_PROXY", "localhost\n127.0.0.1\n") + monkeypatch.delenv("no_proxy", raising=False) + client = DefaultAsyncHttpxClient() + assert client is not None + + async def test_no_proxy_lowercase_with_newlines(self, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("no_proxy", "localhost\n127.0.0.1\n") + monkeypatch.delenv("NO_PROXY", raising=False) + client = DefaultAsyncHttpxClient() + assert client is not None + @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning") async def test_default_client_creation(self) -> None: # Ensure that the client can be initialized without any exceptions