api-key-manager 2.1.2__tar.gz → 3.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/PKG-INFO +62 -6
  2. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/README.md +60 -4
  3. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/api_key_manager.egg-info/PKG-INFO +62 -6
  4. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/api_key_manager.egg-info/SOURCES.txt +7 -0
  5. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/__init__.py +2 -2
  6. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/api_models.py +15 -10
  7. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/checker.py +13 -8
  8. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/cli.py +2 -2
  9. api_key_manager-3.0.0/key_manager/config.py +103 -0
  10. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/detector.py +371 -335
  11. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/logger.py +2 -2
  12. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/parser.py +9 -9
  13. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/__init__.py +9 -1
  14. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/ai302.py +76 -109
  15. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/anthropic.py +109 -109
  16. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/baichuan.py +69 -97
  17. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/base.py +89 -32
  18. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/cerebras.py +76 -109
  19. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/cohere.py +90 -90
  20. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/cstcloud.py +94 -122
  21. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/dashscope.py +92 -120
  22. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/dashscope_coding.py +94 -122
  23. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/deepseek.py +133 -166
  24. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/dmxapi.py +76 -109
  25. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/doubao.py +76 -109
  26. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/fireworks.py +76 -109
  27. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/google.py +99 -99
  28. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/grok.py +75 -109
  29. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/groq.py +76 -109
  30. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/huggingface.py +37 -54
  31. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/hyperbolic.py +76 -109
  32. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/infini.py +102 -135
  33. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/infini_coding.py +0 -28
  34. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/kimi.py +93 -121
  35. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/kimi_coding.py +0 -28
  36. api_key_manager-3.0.0/key_manager/providers/longcat.py +76 -0
  37. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/mimo.py +76 -109
  38. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/mimo_plan.py +107 -140
  39. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/minimax.py +69 -97
  40. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/minimax_plan.py +94 -122
  41. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/mistral.py +76 -109
  42. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/models_registry.py +2962 -2901
  43. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/modelscope.py +120 -134
  44. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/nvidia.py +76 -109
  45. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/ocoolai.py +76 -109
  46. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/openai.py +101 -140
  47. api_key_manager-3.0.0/key_manager/providers/opencode.py +89 -0
  48. api_key_manager-3.0.0/key_manager/providers/opencode_zen.py +89 -0
  49. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/openrouter.py +91 -119
  50. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/perplexity.py +75 -109
  51. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/poe.py +76 -109
  52. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/ppio.py +76 -109
  53. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/replicate.py +37 -54
  54. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/siliconflow.py +93 -121
  55. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/stepfun.py +99 -132
  56. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/tencent_hunyuan.py +94 -122
  57. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/together.py +101 -134
  58. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/yi.py +69 -97
  59. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/zai.py +76 -109
  60. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/zhipu.py +99 -127
  61. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/providers/zhipu_coding.py +0 -28
  62. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/ssrf.py +29 -2
  63. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/storage.py +15 -2
  64. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/tester.py +16 -13
  65. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/validator.py +16 -12
  66. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/web.py +1676 -1557
  67. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/webhook.py +6 -5
  68. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/pyproject.toml +2 -2
  69. api_key_manager-3.0.0/tests/test_api_endpoints.py +685 -0
  70. api_key_manager-3.0.0/tests/test_base_check.py +255 -0
  71. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/tests/test_detector.py +71 -0
  72. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/tests/test_e2e.py +4 -1
  73. api_key_manager-3.0.0/tests/test_parser_supplement.py +216 -0
  74. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/tests/test_provider_detection.py +83 -16
  75. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/tests/test_security.py +3 -3
  76. api_key_manager-3.0.0/tests/test_ssrf_supplement.py +81 -0
  77. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/tests/test_tester.py +46 -20
  78. api_key_manager-3.0.0/tests/test_validator_supplement.py +196 -0
  79. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/tests/test_webhook.py +1 -1
  80. api_key_manager-2.1.2/key_manager/config.py +0 -61
  81. api_key_manager-2.1.2/key_manager/providers/longcat.py +0 -123
  82. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/api_key_manager.egg-info/dependency_links.txt +0 -0
  83. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/api_key_manager.egg-info/entry_points.txt +0 -0
  84. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/api_key_manager.egg-info/requires.txt +0 -0
  85. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/api_key_manager.egg-info/top_level.txt +0 -0
  86. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/__main__.py +0 -0
  87. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/core.py +0 -0
  88. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/errors.py +0 -0
  89. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/i18n.py +0 -0
  90. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/model_capabilities.py +0 -0
  91. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/proxy.py +0 -0
  92. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/py.typed +0 -0
  93. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/key_manager/url_override.py +0 -0
  94. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/setup.cfg +0 -0
  95. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/tests/test_bug_fixes.py +0 -0
  96. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/tests/test_checker.py +0 -0
  97. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/tests/test_core.py +0 -0
  98. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/tests/test_errors.py +0 -0
  99. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/tests/test_i18n.py +0 -0
  100. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/tests/test_logger.py +0 -0
  101. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/tests/test_openapi.py +0 -0
  102. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/tests/test_parser.py +0 -0
  103. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/tests/test_providers.py +0 -0
  104. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/tests/test_proxy.py +0 -0
  105. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/tests/test_storage.py +0 -0
  106. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/tests/test_validator.py +0 -0
  107. {api_key_manager-2.1.2 → api_key_manager-3.0.0}/tests/test_web_fixes.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: api-key-manager
3
- Version: 2.1.2
4
- Summary: Batch manage API keys for 44+ AI providers with CLI and Web interfaces
3
+ Version: 3.0.0
4
+ Summary: Batch manage API keys for 45+ AI providers with CLI and Web interfaces
5
5
  Author: Townrain
6
6
  License: MIT
7
7
  Project-URL: Homepage, https://github.com/Townrain/API-Key-Manager
@@ -45,12 +45,17 @@ Requires-Dist: ruff>=0.4.0; extra == "dev"
45
45
  - **Web 界面** - 赛博朋克风格的管理界面
46
46
  - **代理支持** - 支持 HTTP/SOCKS 代理
47
47
  - **加密存储** - AES-256-GCM 加密存储 API 密钥,随机盐值
48
- - **安全防护** - 路径遍历防护、SSRF 防护、时序安全认证
48
+ - **安全防护** - 路径遍历防护、SSRF 防护(含 IPv6)、时序安全认证、强制认证、CORS 配置
49
49
  - **API 文档** - Swagger UI 和 Redoc 自动文档
50
50
  - **国际化** - 支持中英文错误信息
51
51
  - **SDK 支持** - Python 和 TypeScript 客户端库
52
52
  - **Webhook 通知** - 事件驱动的 Webhook 通知系统
53
53
 
54
+ ## 系统架构
55
+
56
+ ![系统架构流程图](docs/images/flowchart.png)
57
+
58
+
54
59
  ## 支持的 AI 服务商
55
60
 
56
61
  ### 国际
@@ -192,11 +197,13 @@ python web.py
192
197
  ### 安全防护
193
198
 
194
199
  - **路径遍历防护** - 导入端点验证路径在允许目录内
195
- - **SSRF 防护** - `custom_base_url` 验证域名白名单,阻止私有 IP,已接入 `check/single` 和 `balance` 端点
200
+ - **SSRF 防护** - `custom_base_url` 验证域名白名单,阻止私有 IP(含 IPv6),DNS 重绑定防护
196
201
  - **时序安全认证** - 使用 `hmac.compare_digest()` 防止时序攻击
197
- - **认证警告** - 未配置 API Key 时启动警告
202
+ - **强制认证** - 未配置 API Key 时自动生成随机密钥,拒绝未认证请求
198
203
  - **密钥掩码** - API 响应中只返回 `key_masked`,不暴露完整密钥
199
- - **Webhook 安全** - Webhook 端点使用正确的 API 方法,防止运行时错误
204
+ - **CORS 配置** - 跨域资源共享安全配置
205
+ - **Webhook 安全** - 使用 `secrets.token_hex` 生成不可预测的 webhook ID
206
+ - **存储安全** - 密钥文件权限加固 (0o600),AES-256-GCM 加密,PBKDF2 600,000 次迭代
200
207
 
201
208
  ### API 认证
202
209
 
@@ -713,6 +720,55 @@ const result = await client.checkSingleKey({ key: 'sk-xxx', provider: 'openai' }
713
720
 
714
721
  ## 更新日志
715
722
 
723
+
724
+ ### v3.0.0 (2026-06-15)
725
+
726
+ - **安全修复**: 移除 API 响应中的明文密钥,只返回 key_masked
727
+ - **安全修复**: 强制认证,无配置时自动生成随机 API Key
728
+ - **安全修复**: 使用 hmac.compare_digest 防止时序攻击
729
+ - **安全修复**: 添加 CORS 中间件配置
730
+ - **性能优化**: PBKDF2 密钥缓存,迭代次数提升至 600,000 (OWASP 推荐)
731
+ - **性能优化**: 速率限制器添加 TTL 驱逐,防止内存泄漏
732
+ - **可靠性**: 修复 checker.py 合并逻辑错误
733
+ - **可靠性**: 替换所有裸 except 为 except Exception
734
+ - **可靠性**: 添加配置验证和错误处理
735
+ - **可靠性**: 添加 /healthz 健康检查端点
736
+ - **可靠性**: 修复 datetime.utcnow() 废弃警告
737
+ - **SSRF 防护**: 添加 IPv6 私有范围和 DNS 重绑定防护
738
+ - **Webhook 安全**: 使用 secrets.token_hex 替代 MD5 生成 webhook ID
739
+ - **存储安全**: 密钥文件权限加固 (0o600)
740
+ - **前端**: 模型检测列表添加自定义滚轮条样式
741
+ - **前端**: 修复模型类型下拉窗口被弹窗裁剪的问题
742
+ - **精确匹配修复**: 能力检测从子串匹配改为 ^$ 锚定精确匹配
743
+ - **API Bug 修复**: 修复 /api/stats/chart 运行时崩溃
744
+ - **测试补充**: 新增 8 个端点的测试,覆盖率 91.47%
745
+
746
+ ### v2.2.2 (2026-06-14)
747
+
748
+ - **模型检测二级窗口**: 新增模型检测弹窗,支持搜索、类型筛选、全选、批量检测
749
+ - **模型类型图标**: 显示模型能力图标(视觉/推理/联网/工具/嵌入/重排/免费)
750
+ - **模型能力数据更新**: 从 Cherry Studio 同步最新模型能力数据(3184 条记录)
751
+ - **检测逻辑优化**: DeepSeek 欠费密钥正确识别,不再误判为其他服务商
752
+ - **指定模型检测**: 自动检测服务商时也支持指定模型检测
753
+
754
+ ### v2.2.1 (2026-06-14)
755
+
756
+ - **检测逻辑修复**: 自动识别后必须调用 check() 验证 key 有效性
757
+ - **签名匹配修复**: 状态码不再硬编码 401,使用实际状态码
758
+ - **不可靠服务商**: ppio、nvidia、modelscope 不参与加分
759
+ - **模型选择器**: 新增模型下拉框,支持选择具体模型检测
760
+ - **Cherry Studio 同步**: 修复 ownedBy 映射(alibaba→dashscope 等)
761
+ - **测试补充**: 新增 test_parser_supplement、test_ssrf_supplement、test_validator_supplement
762
+ ### v2.2.0 (2026-06-14)
763
+
764
+ - **检测逻辑重构**: 添加三步检测逻辑(/v1/models → 并发测试)
765
+ - **URL 修复**: 从 check_endpoint 提取版本路径,修复 OpenCode 等服务商 404 问题
766
+ - **并发优化**: /v1/models 和 chat/completions 都并发调用
767
+ - **超时控制**: 所有网络请求 5 秒超时
768
+ - **模型同步**: 从 Cherry Studio 同步模型数据,支持 ownedBy 映射
769
+ - **新增服务商**: OpenCode Go、OpenCode Zen
770
+ - **新增服务商**: OpenCode Go、OpenCode Zen
771
+
716
772
  ### v2.1.2 (2026-06-11)
717
773
 
718
774
  - **Bug 修复**: 修复 `KeyManager.detect_provider()` 调用异步函数未 await 的问题
@@ -12,12 +12,17 @@
12
12
  - **Web 界面** - 赛博朋克风格的管理界面
13
13
  - **代理支持** - 支持 HTTP/SOCKS 代理
14
14
  - **加密存储** - AES-256-GCM 加密存储 API 密钥,随机盐值
15
- - **安全防护** - 路径遍历防护、SSRF 防护、时序安全认证
15
+ - **安全防护** - 路径遍历防护、SSRF 防护(含 IPv6)、时序安全认证、强制认证、CORS 配置
16
16
  - **API 文档** - Swagger UI 和 Redoc 自动文档
17
17
  - **国际化** - 支持中英文错误信息
18
18
  - **SDK 支持** - Python 和 TypeScript 客户端库
19
19
  - **Webhook 通知** - 事件驱动的 Webhook 通知系统
20
20
 
21
+ ## 系统架构
22
+
23
+ ![系统架构流程图](docs/images/flowchart.png)
24
+
25
+
21
26
  ## 支持的 AI 服务商
22
27
 
23
28
  ### 国际
@@ -159,11 +164,13 @@ python web.py
159
164
  ### 安全防护
160
165
 
161
166
  - **路径遍历防护** - 导入端点验证路径在允许目录内
162
- - **SSRF 防护** - `custom_base_url` 验证域名白名单,阻止私有 IP,已接入 `check/single` 和 `balance` 端点
167
+ - **SSRF 防护** - `custom_base_url` 验证域名白名单,阻止私有 IP(含 IPv6),DNS 重绑定防护
163
168
  - **时序安全认证** - 使用 `hmac.compare_digest()` 防止时序攻击
164
- - **认证警告** - 未配置 API Key 时启动警告
169
+ - **强制认证** - 未配置 API Key 时自动生成随机密钥,拒绝未认证请求
165
170
  - **密钥掩码** - API 响应中只返回 `key_masked`,不暴露完整密钥
166
- - **Webhook 安全** - Webhook 端点使用正确的 API 方法,防止运行时错误
171
+ - **CORS 配置** - 跨域资源共享安全配置
172
+ - **Webhook 安全** - 使用 `secrets.token_hex` 生成不可预测的 webhook ID
173
+ - **存储安全** - 密钥文件权限加固 (0o600),AES-256-GCM 加密,PBKDF2 600,000 次迭代
167
174
 
168
175
  ### API 认证
169
176
 
@@ -680,6 +687,55 @@ const result = await client.checkSingleKey({ key: 'sk-xxx', provider: 'openai' }
680
687
 
681
688
  ## 更新日志
682
689
 
690
+
691
+ ### v3.0.0 (2026-06-15)
692
+
693
+ - **安全修复**: 移除 API 响应中的明文密钥,只返回 key_masked
694
+ - **安全修复**: 强制认证,无配置时自动生成随机 API Key
695
+ - **安全修复**: 使用 hmac.compare_digest 防止时序攻击
696
+ - **安全修复**: 添加 CORS 中间件配置
697
+ - **性能优化**: PBKDF2 密钥缓存,迭代次数提升至 600,000 (OWASP 推荐)
698
+ - **性能优化**: 速率限制器添加 TTL 驱逐,防止内存泄漏
699
+ - **可靠性**: 修复 checker.py 合并逻辑错误
700
+ - **可靠性**: 替换所有裸 except 为 except Exception
701
+ - **可靠性**: 添加配置验证和错误处理
702
+ - **可靠性**: 添加 /healthz 健康检查端点
703
+ - **可靠性**: 修复 datetime.utcnow() 废弃警告
704
+ - **SSRF 防护**: 添加 IPv6 私有范围和 DNS 重绑定防护
705
+ - **Webhook 安全**: 使用 secrets.token_hex 替代 MD5 生成 webhook ID
706
+ - **存储安全**: 密钥文件权限加固 (0o600)
707
+ - **前端**: 模型检测列表添加自定义滚轮条样式
708
+ - **前端**: 修复模型类型下拉窗口被弹窗裁剪的问题
709
+ - **精确匹配修复**: 能力检测从子串匹配改为 ^$ 锚定精确匹配
710
+ - **API Bug 修复**: 修复 /api/stats/chart 运行时崩溃
711
+ - **测试补充**: 新增 8 个端点的测试,覆盖率 91.47%
712
+
713
+ ### v2.2.2 (2026-06-14)
714
+
715
+ - **模型检测二级窗口**: 新增模型检测弹窗,支持搜索、类型筛选、全选、批量检测
716
+ - **模型类型图标**: 显示模型能力图标(视觉/推理/联网/工具/嵌入/重排/免费)
717
+ - **模型能力数据更新**: 从 Cherry Studio 同步最新模型能力数据(3184 条记录)
718
+ - **检测逻辑优化**: DeepSeek 欠费密钥正确识别,不再误判为其他服务商
719
+ - **指定模型检测**: 自动检测服务商时也支持指定模型检测
720
+
721
+ ### v2.2.1 (2026-06-14)
722
+
723
+ - **检测逻辑修复**: 自动识别后必须调用 check() 验证 key 有效性
724
+ - **签名匹配修复**: 状态码不再硬编码 401,使用实际状态码
725
+ - **不可靠服务商**: ppio、nvidia、modelscope 不参与加分
726
+ - **模型选择器**: 新增模型下拉框,支持选择具体模型检测
727
+ - **Cherry Studio 同步**: 修复 ownedBy 映射(alibaba→dashscope 等)
728
+ - **测试补充**: 新增 test_parser_supplement、test_ssrf_supplement、test_validator_supplement
729
+ ### v2.2.0 (2026-06-14)
730
+
731
+ - **检测逻辑重构**: 添加三步检测逻辑(/v1/models → 并发测试)
732
+ - **URL 修复**: 从 check_endpoint 提取版本路径,修复 OpenCode 等服务商 404 问题
733
+ - **并发优化**: /v1/models 和 chat/completions 都并发调用
734
+ - **超时控制**: 所有网络请求 5 秒超时
735
+ - **模型同步**: 从 Cherry Studio 同步模型数据,支持 ownedBy 映射
736
+ - **新增服务商**: OpenCode Go、OpenCode Zen
737
+ - **新增服务商**: OpenCode Go、OpenCode Zen
738
+
683
739
  ### v2.1.2 (2026-06-11)
684
740
 
685
741
  - **Bug 修复**: 修复 `KeyManager.detect_provider()` 调用异步函数未 await 的问题
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: api-key-manager
3
- Version: 2.1.2
4
- Summary: Batch manage API keys for 44+ AI providers with CLI and Web interfaces
3
+ Version: 3.0.0
4
+ Summary: Batch manage API keys for 45+ AI providers with CLI and Web interfaces
5
5
  Author: Townrain
6
6
  License: MIT
7
7
  Project-URL: Homepage, https://github.com/Townrain/API-Key-Manager
@@ -45,12 +45,17 @@ Requires-Dist: ruff>=0.4.0; extra == "dev"
45
45
  - **Web 界面** - 赛博朋克风格的管理界面
46
46
  - **代理支持** - 支持 HTTP/SOCKS 代理
47
47
  - **加密存储** - AES-256-GCM 加密存储 API 密钥,随机盐值
48
- - **安全防护** - 路径遍历防护、SSRF 防护、时序安全认证
48
+ - **安全防护** - 路径遍历防护、SSRF 防护(含 IPv6)、时序安全认证、强制认证、CORS 配置
49
49
  - **API 文档** - Swagger UI 和 Redoc 自动文档
50
50
  - **国际化** - 支持中英文错误信息
51
51
  - **SDK 支持** - Python 和 TypeScript 客户端库
52
52
  - **Webhook 通知** - 事件驱动的 Webhook 通知系统
53
53
 
54
+ ## 系统架构
55
+
56
+ ![系统架构流程图](docs/images/flowchart.png)
57
+
58
+
54
59
  ## 支持的 AI 服务商
55
60
 
56
61
  ### 国际
@@ -192,11 +197,13 @@ python web.py
192
197
  ### 安全防护
193
198
 
194
199
  - **路径遍历防护** - 导入端点验证路径在允许目录内
195
- - **SSRF 防护** - `custom_base_url` 验证域名白名单,阻止私有 IP,已接入 `check/single` 和 `balance` 端点
200
+ - **SSRF 防护** - `custom_base_url` 验证域名白名单,阻止私有 IP(含 IPv6),DNS 重绑定防护
196
201
  - **时序安全认证** - 使用 `hmac.compare_digest()` 防止时序攻击
197
- - **认证警告** - 未配置 API Key 时启动警告
202
+ - **强制认证** - 未配置 API Key 时自动生成随机密钥,拒绝未认证请求
198
203
  - **密钥掩码** - API 响应中只返回 `key_masked`,不暴露完整密钥
199
- - **Webhook 安全** - Webhook 端点使用正确的 API 方法,防止运行时错误
204
+ - **CORS 配置** - 跨域资源共享安全配置
205
+ - **Webhook 安全** - 使用 `secrets.token_hex` 生成不可预测的 webhook ID
206
+ - **存储安全** - 密钥文件权限加固 (0o600),AES-256-GCM 加密,PBKDF2 600,000 次迭代
200
207
 
201
208
  ### API 认证
202
209
 
@@ -713,6 +720,55 @@ const result = await client.checkSingleKey({ key: 'sk-xxx', provider: 'openai' }
713
720
 
714
721
  ## 更新日志
715
722
 
723
+
724
+ ### v3.0.0 (2026-06-15)
725
+
726
+ - **安全修复**: 移除 API 响应中的明文密钥,只返回 key_masked
727
+ - **安全修复**: 强制认证,无配置时自动生成随机 API Key
728
+ - **安全修复**: 使用 hmac.compare_digest 防止时序攻击
729
+ - **安全修复**: 添加 CORS 中间件配置
730
+ - **性能优化**: PBKDF2 密钥缓存,迭代次数提升至 600,000 (OWASP 推荐)
731
+ - **性能优化**: 速率限制器添加 TTL 驱逐,防止内存泄漏
732
+ - **可靠性**: 修复 checker.py 合并逻辑错误
733
+ - **可靠性**: 替换所有裸 except 为 except Exception
734
+ - **可靠性**: 添加配置验证和错误处理
735
+ - **可靠性**: 添加 /healthz 健康检查端点
736
+ - **可靠性**: 修复 datetime.utcnow() 废弃警告
737
+ - **SSRF 防护**: 添加 IPv6 私有范围和 DNS 重绑定防护
738
+ - **Webhook 安全**: 使用 secrets.token_hex 替代 MD5 生成 webhook ID
739
+ - **存储安全**: 密钥文件权限加固 (0o600)
740
+ - **前端**: 模型检测列表添加自定义滚轮条样式
741
+ - **前端**: 修复模型类型下拉窗口被弹窗裁剪的问题
742
+ - **精确匹配修复**: 能力检测从子串匹配改为 ^$ 锚定精确匹配
743
+ - **API Bug 修复**: 修复 /api/stats/chart 运行时崩溃
744
+ - **测试补充**: 新增 8 个端点的测试,覆盖率 91.47%
745
+
746
+ ### v2.2.2 (2026-06-14)
747
+
748
+ - **模型检测二级窗口**: 新增模型检测弹窗,支持搜索、类型筛选、全选、批量检测
749
+ - **模型类型图标**: 显示模型能力图标(视觉/推理/联网/工具/嵌入/重排/免费)
750
+ - **模型能力数据更新**: 从 Cherry Studio 同步最新模型能力数据(3184 条记录)
751
+ - **检测逻辑优化**: DeepSeek 欠费密钥正确识别,不再误判为其他服务商
752
+ - **指定模型检测**: 自动检测服务商时也支持指定模型检测
753
+
754
+ ### v2.2.1 (2026-06-14)
755
+
756
+ - **检测逻辑修复**: 自动识别后必须调用 check() 验证 key 有效性
757
+ - **签名匹配修复**: 状态码不再硬编码 401,使用实际状态码
758
+ - **不可靠服务商**: ppio、nvidia、modelscope 不参与加分
759
+ - **模型选择器**: 新增模型下拉框,支持选择具体模型检测
760
+ - **Cherry Studio 同步**: 修复 ownedBy 映射(alibaba→dashscope 等)
761
+ - **测试补充**: 新增 test_parser_supplement、test_ssrf_supplement、test_validator_supplement
762
+ ### v2.2.0 (2026-06-14)
763
+
764
+ - **检测逻辑重构**: 添加三步检测逻辑(/v1/models → 并发测试)
765
+ - **URL 修复**: 从 check_endpoint 提取版本路径,修复 OpenCode 等服务商 404 问题
766
+ - **并发优化**: /v1/models 和 chat/completions 都并发调用
767
+ - **超时控制**: 所有网络请求 5 秒超时
768
+ - **模型同步**: 从 Cherry Studio 同步模型数据,支持 ownedBy 映射
769
+ - **新增服务商**: OpenCode Go、OpenCode Zen
770
+ - **新增服务商**: OpenCode Go、OpenCode Zen
771
+
716
772
  ### v2.1.2 (2026-06-11)
717
773
 
718
774
  - **Bug 修复**: 修复 `KeyManager.detect_provider()` 调用异步函数未 await 的问题
@@ -62,6 +62,8 @@ key_manager/providers/modelscope.py
62
62
  key_manager/providers/nvidia.py
63
63
  key_manager/providers/ocoolai.py
64
64
  key_manager/providers/openai.py
65
+ key_manager/providers/opencode.py
66
+ key_manager/providers/opencode_zen.py
65
67
  key_manager/providers/openrouter.py
66
68
  key_manager/providers/perplexity.py
67
69
  key_manager/providers/poe.py
@@ -75,6 +77,8 @@ key_manager/providers/yi.py
75
77
  key_manager/providers/zai.py
76
78
  key_manager/providers/zhipu.py
77
79
  key_manager/providers/zhipu_coding.py
80
+ tests/test_api_endpoints.py
81
+ tests/test_base_check.py
78
82
  tests/test_bug_fixes.py
79
83
  tests/test_checker.py
80
84
  tests/test_core.py
@@ -85,12 +89,15 @@ tests/test_i18n.py
85
89
  tests/test_logger.py
86
90
  tests/test_openapi.py
87
91
  tests/test_parser.py
92
+ tests/test_parser_supplement.py
88
93
  tests/test_provider_detection.py
89
94
  tests/test_providers.py
90
95
  tests/test_proxy.py
91
96
  tests/test_security.py
97
+ tests/test_ssrf_supplement.py
92
98
  tests/test_storage.py
93
99
  tests/test_tester.py
94
100
  tests/test_validator.py
101
+ tests/test_validator_supplement.py
95
102
  tests/test_web_fixes.py
96
103
  tests/test_webhook.py
@@ -1,4 +1,4 @@
1
- """Key Manager - Batch API key management for 44+ AI providers."""
1
+ """Key Manager - Batch API key management for 45+ AI providers."""
2
2
  from key_manager.core import KeyManager
3
3
  from key_manager.providers import PROVIDERS, get_display_name
4
4
  from key_manager.errors import KeyManagerError, ErrorCode
@@ -13,5 +13,5 @@ __all__ = [
13
13
  "KeyStore",
14
14
  ]
15
15
 
16
- __version__ = "2.1.2"
16
+ __version__ = "3.0.0"
17
17
 
@@ -71,6 +71,7 @@ class KeyListResponse(BaseModel):
71
71
 
72
72
 
73
73
  class KeyExportItem(BaseModel):
74
+ key_masked: str = Field(..., description="Masked key for display")
74
75
  provider: str = Field(..., description="Provider identifier")
75
76
  max_tokens: int | None = Field(None, description="Maximum token limit")
76
77
  max_concurrency: int | None = Field(None, description="Maximum concurrency limit")
@@ -111,11 +112,12 @@ class ImportResponse(BaseModel):
111
112
  class CheckSingleRequest(BaseModel):
112
113
  key: str = Field(..., min_length=1, description="API key to check")
113
114
  provider: str = Field("", description="Provider name (auto-detected if empty)")
115
+ model: str | None = Field(None, description="Specific model to test (tests all if empty)")
114
116
  custom_base_url: str | None = Field(None, description="Override provider base URL")
115
117
 
116
118
  model_config = {
117
119
  "json_schema_extra": {
118
- "examples": [{"key": "sk-abc123", "provider": "openai"}]
120
+ "examples": [{"key": "sk-abc123", "provider": "openai", "model": "gpt-4o"}]
119
121
  }
120
122
  }
121
123
 
@@ -296,23 +298,26 @@ class StatsResponse(BaseModel):
296
298
  total: int = Field(0, description="Total key count")
297
299
 
298
300
 
299
- class StatsChartProviderEntry(BaseModel):
300
- total: int = Field(0, description="Total keys for this provider")
301
- valid: int = Field(0, description="Valid keys")
302
- invalid: int = Field(0, description="Invalid keys")
303
- error: int = Field(0, description="Errored keys")
304
-
305
-
306
301
  class StatsChartStatuses(BaseModel):
307
302
  valid: int = Field(0, description="Total valid keys")
308
303
  invalid: int = Field(0, description="Total invalid keys")
309
304
  error: int = Field(0, description="Total errored keys")
310
305
  unknown: int = Field(0, description="Total unknown-status keys")
311
306
 
307
+ class StatsChartProviderEntry(BaseModel):
308
+ provider: str = Field(..., description="Provider identifier")
309
+ display_name: str = Field("", description="Human-readable provider name")
310
+ total: int = Field(0, description="Total keys for this provider")
311
+ valid: int = Field(0, description="Valid keys")
312
+ invalid: int = Field(0, description="Invalid keys")
313
+ error: int = Field(0, description="Errored keys")
314
+ statuses: StatsChartStatuses = Field(
315
+ default_factory=StatsChartStatuses, description="Status breakdown"
316
+ )
312
317
 
313
318
  class StatsChartResponse(BaseModel):
314
- providers: dict[str, StatsChartProviderEntry] = Field(
315
- default_factory=dict, description="Per-provider breakdown for charts"
319
+ providers: list[StatsChartProviderEntry] = Field(
320
+ default_factory=list, description="Per-provider breakdown for charts"
316
321
  )
317
322
  statuses: StatsChartStatuses = Field(
318
323
  default_factory=StatsChartStatuses, description="Global status counts"
@@ -1,14 +1,11 @@
1
1
  import asyncio
2
- import json
3
2
  from datetime import datetime
4
- from pathlib import Path
5
3
 
6
4
  import httpx
7
5
 
8
6
  from .providers import PROVIDERS
9
7
  from .validator import validate_keys
10
8
 
11
-
12
9
  async def run_check(keys_file: str = "./data/keys.json",
13
10
  results_file: str = "./data/check_results.json",
14
11
  logs_dir: str = "./data/logs",
@@ -16,14 +13,16 @@ async def run_check(keys_file: str = "./data/keys.json",
16
13
  timeout: int = 30,
17
14
  proxy: str = None,
18
15
  retry_failed: bool = True,
19
- retry_count: int = 2) -> dict:
16
+ retry_count: int = 2,
17
+ config: dict = None) -> dict:
20
18
  results = await validate_keys(
21
19
  keys_file=keys_file,
22
20
  results_file=results_file,
23
21
  logs_dir=logs_dir,
24
22
  concurrency=concurrency,
25
23
  timeout=timeout,
26
- proxy=proxy
24
+ proxy=proxy,
25
+ config=config
27
26
  )
28
27
 
29
28
  # Retry failed keys
@@ -40,12 +39,18 @@ async def run_check(keys_file: str = "./data/keys.json",
40
39
  concurrency=concurrency,
41
40
  timeout=timeout,
42
41
  proxy=proxy,
43
- status_filter="error"
42
+ status_filter="error",
43
+ config=config
44
44
  )
45
45
 
46
- # Merge results
46
+ # Recompute summary from details to avoid double-counting
47
+ # The retry only re-checks error keys, so we need to update the summary
48
+ # based on what changed
49
+ results["summary"]["error"]["count"] = retry_results["summary"]["error"]["count"]
50
+ results["summary"]["error"]["keys"] = retry_results["summary"]["error"]["keys"]
47
51
  results["summary"]["valid"]["count"] += retry_results["summary"]["valid"]["count"]
52
+ results["summary"]["valid"]["keys"].extend(retry_results["summary"]["valid"]["keys"])
48
53
  results["summary"]["invalid"]["count"] += retry_results["summary"]["invalid"]["count"]
49
- results["summary"]["error"]["count"] = retry_results["summary"]["error"]["count"]
54
+ results["summary"]["invalid"]["keys"].extend(retry_results["summary"]["invalid"]["keys"])
50
55
 
51
56
  return results
@@ -2,7 +2,7 @@ import argparse
2
2
  import asyncio
3
3
  import json
4
4
  import sys
5
- from datetime import datetime, timedelta
5
+ from datetime import datetime, timedelta, timezone
6
6
  from pathlib import Path
7
7
 
8
8
  from rich.console import Console
@@ -153,7 +153,7 @@ def cmd_report(args, config):
153
153
  return
154
154
 
155
155
  days = args.days or 7
156
- cutoff = datetime.utcnow() - timedelta(days=days)
156
+ cutoff = datetime.now(timezone.utc) - timedelta(days=days)
157
157
 
158
158
  stats = {}
159
159
  for key, info in data["keys"].items():
@@ -0,0 +1,103 @@
1
+ import copy
2
+ import logging
3
+ import yaml
4
+ from pathlib import Path
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ DEFAULT_CONFIG = {
9
+ "scan": {
10
+ "directories": ["./data/input"],
11
+ "recursive": True,
12
+ },
13
+ "check": {
14
+ "interval_hours": 6,
15
+ "timeout_seconds": 30,
16
+ "concurrency": 100,
17
+ "retry_failed": True,
18
+ "retry_count": 2,
19
+ },
20
+ "test": {
21
+ "token_test": True,
22
+ "token_auto_detect": True,
23
+ "token_steps": [1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576],
24
+ "token_max_manual": None,
25
+ "concurrency_test": True,
26
+ "concurrency_steps": [1, 5, 10, 20, 50, 100],
27
+ "concurrency_timeout_seconds": 120,
28
+ },
29
+ "storage": {
30
+ "keys_file": "./data/keys.json",
31
+ "check_results_file": "./data/check_results.json",
32
+ "test_results_file": "./data/test_results.json",
33
+ "logs_dir": "./data/logs",
34
+ },
35
+ "auth": {
36
+ "api_key": "",
37
+ },
38
+ "rate_limit": {
39
+ "requests_per_minute": 60,
40
+ },
41
+ }
42
+
43
+
44
+ def _deep_merge(base: dict, override: dict) -> dict:
45
+ result = base.copy()
46
+ for key, value in override.items():
47
+ if key in result and isinstance(result[key], dict) and isinstance(value, dict):
48
+ result[key] = _deep_merge(result[key], value)
49
+ else:
50
+ result[key] = value
51
+ return result
52
+
53
+
54
+ def _validate_config(config: dict) -> dict:
55
+ """Validate config values and warn about unknown keys."""
56
+ valid_keys = set(DEFAULT_CONFIG.keys())
57
+ unknown_keys = set(config.keys()) - valid_keys
58
+ if unknown_keys:
59
+ logger.warning(f"Unknown config keys (ignored): {unknown_keys}")
60
+
61
+ # Validate numeric values
62
+ check_config = config.get("check", {})
63
+ if check_config.get("concurrency", 100) < 1:
64
+ logger.warning("check.concurrency must be >= 1, using default")
65
+ check_config["concurrency"] = 100
66
+ if check_config.get("timeout_seconds", 30) < 1:
67
+ logger.warning("check.timeout_seconds must be >= 1, using default")
68
+ check_config["timeout_seconds"] = 30
69
+
70
+ rate_limit = config.get("rate_limit", {})
71
+ if rate_limit.get("requests_per_minute", 60) < 0:
72
+ logger.warning("rate_limit.requests_per_minute must be >= 0, using default")
73
+ rate_limit["requests_per_minute"] = 60
74
+
75
+ return config
76
+
77
+
78
+ def load_config(path: str = "config.yaml") -> dict:
79
+ config_path = Path(path)
80
+ if config_path.exists():
81
+ try:
82
+ with open(config_path, "r", encoding="utf-8") as f:
83
+ user_config = yaml.safe_load(f) or {}
84
+ if not isinstance(user_config, dict):
85
+ logger.warning(f"Config file {path} is not a dict, using defaults")
86
+ return copy.deepcopy(DEFAULT_CONFIG)
87
+ config = _deep_merge(DEFAULT_CONFIG, user_config)
88
+ return _validate_config(config)
89
+ except yaml.YAMLError as e:
90
+ logger.error(f"Failed to parse config file {path}: {e}")
91
+ return copy.deepcopy(DEFAULT_CONFIG)
92
+ except Exception as e:
93
+ logger.error(f"Failed to load config file {path}: {e}")
94
+ return copy.deepcopy(DEFAULT_CONFIG)
95
+ return copy.deepcopy(DEFAULT_CONFIG)
96
+
97
+
98
+ def save_config(config: dict, path: str = "config.yaml"):
99
+ try:
100
+ with open(path, "w", encoding="utf-8") as f:
101
+ yaml.dump(config, f, default_flow_style=False, allow_unicode=True)
102
+ except Exception as e:
103
+ logger.error(f"Failed to save config file {path}: {e}")