diff --git a/nanobot/providers/openai_compat_provider.py b/nanobot/providers/openai_compat_provider.py index 4254a0323..37fce20d0 100644 --- a/nanobot/providers/openai_compat_provider.py +++ b/nanobot/providers/openai_compat_provider.py @@ -523,9 +523,6 @@ class OpenAICompatProvider(LLMProvider): self._proxy = proxy or None self._native_compaction_available = True - if api_key and spec and spec.env_key: - self._setup_env(api_key, api_base) - effective_base = api_base or (spec.default_api_base if spec else None) or None self._effective_base = effective_base self._default_headers = {"x-session-affinity": uuid.uuid4().hex} @@ -618,20 +615,6 @@ class OpenAICompatProvider(LLMProvider): raise RuntimeError("OpenAI client initialization did not produce a client") return self._client - def _setup_env(self, api_key: str, api_base: str | None) -> None: - """Set environment variables based on provider spec.""" - spec = self._spec - if not spec or not spec.env_key: - return - if spec.is_gateway: - os.environ[spec.env_key] = api_key - else: - os.environ.setdefault(spec.env_key, api_key) - effective_base = api_base or spec.default_api_base - for env_name, env_val in spec.env_extras: - resolved = env_val.replace("{api_key}", api_key).replace("{api_base}", effective_base) - os.environ.setdefault(env_name, resolved) - @classmethod def _apply_cache_control( cls, diff --git a/tests/providers/test_provider_env_isolation.py b/tests/providers/test_provider_env_isolation.py new file mode 100644 index 000000000..c90f2efbb --- /dev/null +++ b/tests/providers/test_provider_env_isolation.py @@ -0,0 +1,48 @@ +"""Provider credentials must not leak through process-global os.environ.""" + +from __future__ import annotations + +import os + +from nanobot.providers.openai_compat_provider import OpenAICompatProvider +from nanobot.providers.registry import find_by_name + + +def test_provider_init_does_not_mutate_shared_env_keys(monkeypatch) -> None: + """Multi-provider setups must not overwrite or pin each other's keys.""" + monkeypatch.delenv("OPENAI_API_KEY", raising=False) + monkeypatch.delenv("OPENROUTER_API_KEY", raising=False) + + openai_spec = find_by_name("openai") + openrouter_spec = find_by_name("openrouter") + assert openai_spec is not None and openrouter_spec is not None + + OpenAICompatProvider( + api_key="sk-openai-secret", + default_model="gpt-4o", + spec=openai_spec, + ) + OpenAICompatProvider( + api_key="sk-or-secret", + default_model="openrouter/auto", + spec=openrouter_spec, + api_base="https://openrouter.ai/api/v1", + ) + + assert "OPENAI_API_KEY" not in os.environ + assert "OPENROUTER_API_KEY" not in os.environ + + +def test_provider_init_preserves_preexisting_env_keys(monkeypatch) -> None: + monkeypatch.setenv("OPENAI_API_KEY", "preexisting-user-key") + + openai_spec = find_by_name("openai") + assert openai_spec is not None + provider = OpenAICompatProvider( + api_key="sk-from-config", + default_model="gpt-4o", + spec=openai_spec, + ) + + assert os.environ["OPENAI_API_KEY"] == "preexisting-user-key" + assert provider._api_key_for_client == "sk-from-config"