diff --git a/strix/interface/scan_setup.py b/strix/interface/scan_setup.py index 7b22250a..838a1d1e 100644 --- a/strix/interface/scan_setup.py +++ b/strix/interface/scan_setup.py @@ -66,14 +66,26 @@ def _first_non_ascii(value: str) -> tuple[int, str] | None: return None +def _header_candidates( + prefix: str, api_key: str | None, extra_headers: dict[str, str] | None +) -> list[tuple[str, str]]: + candidates: list[tuple[str, str]] = [] + if api_key: + candidates.append((f"{prefix}LLM_API_KEY", api_key)) + for header, value in (extra_headers or {}).items(): + candidates.append((f"{prefix}LLM_EXTRA_HEADERS header name {header!r}", header)) + candidates.append((f"{prefix}LLM_EXTRA_HEADERS value for {header!r}", value)) + return candidates + + def check_header_safe_credentials(settings: Settings) -> None: llm = settings.llm + dedupe = settings.dedupe candidates: list[tuple[str, str]] = [] - if llm.api_key: - candidates.append(("LLM_API_KEY", llm.api_key)) - for header, value in (llm.extra_headers or {}).items(): - candidates.append((f"LLM_EXTRA_HEADERS header name {header!r}", header)) - candidates.append((f"LLM_EXTRA_HEADERS value for {header!r}", value)) + if not codex.subscription_model(llm.model): + candidates += _header_candidates("", llm.api_key, llm.extra_headers) + if dedupe.model and not codex.subscription_model(dedupe.model): + candidates += _header_candidates("DEDUPE_", dedupe.api_key, dedupe.extra_headers) for setting, value in candidates: found = _first_non_ascii(value) if found is None: diff --git a/tests/test_preflight_header_values.py b/tests/test_preflight_header_values.py index 4e99a993..21c0c531 100644 --- a/tests/test_preflight_header_values.py +++ b/tests/test_preflight_header_values.py @@ -8,7 +8,16 @@ from strix.config import Settings from strix.interface.scan_setup import check_header_safe_credentials, preflight_model_connection -_ENV_KEYS = ("LLM_API_KEY", "OPENAI_API_KEY", "LLM_EXTRA_HEADERS", "LLM_API_BASE", "STRIX_LLM") +_ENV_KEYS = ( + "LLM_API_KEY", + "OPENAI_API_KEY", + "LLM_EXTRA_HEADERS", + "LLM_API_BASE", + "STRIX_LLM", + "STRIX_DEDUPE_MODEL", + "DEDUPE_LLM_API_KEY", + "DEDUPE_LLM_EXTRA_HEADERS", +) @pytest.fixture(autouse=True) @@ -67,6 +76,32 @@ def test_extra_header_value_is_checked(monkeypatch: pytest.MonkeyPatch) -> None: check_header_safe_credentials(settings) +def test_subscription_model_ignores_an_unused_main_key(monkeypatch: pytest.MonkeyPatch) -> None: + settings = _settings(monkeypatch, STRIX_LLM="chatgpt/gpt-5", LLM_API_KEY="sk-abc\u201d") + + check_header_safe_credentials(settings) + + +def test_dedupe_key_is_checked_when_a_dedupe_model_is_set( + monkeypatch: pytest.MonkeyPatch, +) -> None: + settings = _settings( + monkeypatch, + LLM_API_KEY="sk-plain", + STRIX_DEDUPE_MODEL="openai/gpt-4o-mini", + DEDUPE_LLM_API_KEY="sk-dedupe\u00a0", + ) + + with pytest.raises(ValueError, match=r"DEDUPE_LLM_API_KEY.*U\+00A0"): + check_header_safe_credentials(settings) + + +def test_dedupe_key_is_ignored_without_a_dedupe_model(monkeypatch: pytest.MonkeyPatch) -> None: + settings = _settings(monkeypatch, LLM_API_KEY="sk-plain", DEDUPE_LLM_API_KEY="sk-dedupe\u00a0") + + check_header_safe_credentials(settings) + + def test_preflight_rejects_the_key_before_any_request(monkeypatch: pytest.MonkeyPatch) -> None: settings = _settings( monkeypatch,