Skip to content

Commit b9fea9a

Browse files
committed
fix(review-feedback-933): address latest review comments
1 parent 769f979 commit b9fea9a

3 files changed

Lines changed: 30 additions & 7 deletions

File tree

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
7272
- [修复] 飞书群机器人通知现在支持 `FEISHU_WEBHOOK_SECRET` / `FEISHU_WEBHOOK_KEYWORD`,并在 Web 设置与文档中明确区分 Webhook 推送和 `FEISHU_APP_ID` / `FEISHU_APP_SECRET` 应用模式,降低误配导致的推送失败。
7373
- [修复] `LLM_CHANNELS` 现在会在常见 provider 渠道缺少 `LLM_{NAME}_API_KEY(S)` 时,安全回退读取匹配的 legacy Secrets(如 `DEEPSEEK_API_KEY``AIHUBMIX_KEY``OPENAI_API_KEY``GEMINI_API_KEY``ANTHROPIC_API_KEY`),让 GitHub Actions 可继续使用默认 `.env` 的非敏感渠道结构。
7474
- [修复] `LLM_CHANNELS` 现在会在常见 provider 渠道缺少 `LLM_{NAME}_API_KEY(S)` 时,安全回退读取匹配的 legacy Secrets(如 `DEEPSEEK_API_KEY``AIHUBMIX_KEY``OPENAI_API_KEY``GEMINI_API_KEY``ANTHROPIC_API_KEY`),让 GitHub Actions 可继续使用默认 `.env` 的非敏感渠道结构。当渠道配置了自定义 `base_url` 时,仅通过严格域名校验匹配 legacy 密钥,防止凭据泄露到非官方端点。
75+
- [修复] `LLM_CHANNELS` 现在会在常见 provider 渠道缺少 `LLM_{NAME}_API_KEY(S)` 时,安全回退读取匹配的 legacy Secrets(如 `DEEPSEEK_API_KEY``AIHUBMIX_KEY``OPENAI_API_KEY``GEMINI_API_KEY``ANTHROPIC_API_KEY`),让 GitHub Actions 可继续使用默认 `.env` 的非敏感渠道结构。当渠道配置了自定义 `base_url` 时,仅当 URL 使用 HTTPS 协议且通过严格域名校验时才匹配 legacy 密钥,防止凭据通过明文 HTTP 或非官方端点泄露。
7576
- [文档] 更新中英文 LLM 配置指南与 FAQ,补充 GitHub Actions 下渠道模式复用 legacy Secrets 的用法,并明确自定义渠道仍建议使用 `LITELLM_CONFIG` + `LITELLM_CONFIG_YAML`
7677

7778
## [3.11.0] - 2026-03-27

src/config.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,33 +99,38 @@ def resolve_channel_legacy_api_keys(channel_name: str, base_url: Optional[str])
9999
"""
100100
normalized_name = canonicalize_llm_channel_protocol(channel_name)
101101
raw_name = (channel_name or "").strip().lower()
102-
host = (urlparse(base_url or "").hostname or "").lower()
102+
parsed_url = urlparse(base_url or "")
103+
host = (parsed_url.hostname or "").lower()
104+
scheme = (parsed_url.scheme or "").lower()
103105
# Name-based fallback is only safe when no custom base_url is configured,
104106
# because requests will go to the provider's default endpoint. When a
105107
# base_url IS present, only trust the strict host check to prevent legacy
106108
# secrets from leaking to attacker-controlled endpoints.
107109
has_custom_url = bool(base_url and base_url.strip())
110+
# Reject non-HTTPS custom URLs for host-based fallback to prevent
111+
# credential exposure over plaintext HTTP connections.
112+
host_fallback_ok = has_custom_url and scheme == "https"
108113

109-
if _is_provider_host(host, "aihubmix.com") or (not has_custom_url and raw_name == "aihubmix"):
114+
if (host_fallback_ok and _is_provider_host(host, "aihubmix.com")) or (not has_custom_url and raw_name == "aihubmix"):
110115
aihubmix_key = os.getenv('AIHUBMIX_KEY', '').strip()
111116
if aihubmix_key:
112117
return [aihubmix_key], 'AIHUBMIX_KEY'
113118
return read_env_key_list('OPENAI_API_KEYS', 'OPENAI_API_KEY')
114119

115-
if _is_provider_host(host, "deepseek.com") or (not has_custom_url and normalized_name == "deepseek"):
120+
if (host_fallback_ok and _is_provider_host(host, "deepseek.com")) or (not has_custom_url and normalized_name == "deepseek"):
116121
return read_env_key_list('DEEPSEEK_API_KEYS', 'DEEPSEEK_API_KEY')
117122

118123
if (
119-
_is_provider_host(host, "generativelanguage.googleapis.com")
120-
or _is_provider_host(host, "aiplatform.googleapis.com")
124+
(host_fallback_ok and _is_provider_host(host, "generativelanguage.googleapis.com"))
125+
or (host_fallback_ok and _is_provider_host(host, "aiplatform.googleapis.com"))
121126
or (not has_custom_url and normalized_name in {"gemini", "vertex_ai"})
122127
):
123128
return read_env_key_list('GEMINI_API_KEYS', 'GEMINI_API_KEY')
124129

125-
if _is_provider_host(host, "anthropic.com") or (not has_custom_url and normalized_name == "anthropic"):
130+
if (host_fallback_ok and _is_provider_host(host, "anthropic.com")) or (not has_custom_url and normalized_name == "anthropic"):
126131
return read_env_key_list('ANTHROPIC_API_KEYS', 'ANTHROPIC_API_KEY')
127132

128-
if _is_provider_host(host, "openai.com") or (not has_custom_url and normalized_name == "openai"):
133+
if (host_fallback_ok and _is_provider_host(host, "openai.com")) or (not has_custom_url and normalized_name == "openai"):
129134
return read_env_key_list('OPENAI_API_KEYS', 'OPENAI_API_KEY')
130135

131136
return [], None

tests/test_llm_channel_config.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,23 @@ def test_provider_named_channel_with_spoofed_base_url_blocks_legacy_key(self, _m
282282
self.assertEqual(config.llm_channels, [])
283283
self.assertEqual(config.llm_models_source, "legacy_env")
284284

285+
@patch("src.config.setup_env")
286+
@patch.object(Config, "_parse_litellm_yaml", return_value=[])
287+
def test_http_base_url_blocks_legacy_key_fallback(self, _mock_parse_yaml, _mock_setup_env) -> None:
288+
"""Plaintext HTTP base URLs must not trigger host-based legacy key fallback."""
289+
env = {
290+
"LLM_CHANNELS": "openai",
291+
"LLM_OPENAI_BASE_URL": "http://api.openai.com/v1",
292+
"LLM_OPENAI_MODELS": "gpt-4o-mini",
293+
"OPENAI_API_KEY": "sk-openai-secret",
294+
}
295+
296+
with patch.dict(os.environ, env, clear=True):
297+
config = Config._load_from_env()
298+
299+
self.assertEqual(config.llm_channels, [])
300+
self.assertEqual(config.llm_models_source, "legacy_env")
301+
285302
@patch("src.config.setup_env")
286303
@patch.object(Config, "_parse_litellm_yaml", return_value=[])
287304
def test_custom_openai_compatible_channel_does_not_fall_back_to_legacy_keys(self, _mock_parse_yaml, _mock_setup_env) -> None:

0 commit comments

Comments
 (0)