api-key-manager 2.2.0__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 (106) hide show
  1. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/PKG-INFO +47 -6
  2. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/README.md +45 -4
  3. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/api_key_manager.egg-info/PKG-INFO +47 -6
  4. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/api_key_manager.egg-info/SOURCES.txt +3 -0
  5. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/__init__.py +2 -2
  6. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/api_models.py +15 -10
  7. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/checker.py +13 -8
  8. {api_key_manager-2.2.0 → 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.2.0 → api_key_manager-3.0.0}/key_manager/detector.py +371 -363
  11. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/logger.py +2 -2
  12. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/parser.py +9 -9
  13. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/ai302.py +76 -76
  14. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/anthropic.py +109 -109
  15. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/baichuan.py +69 -69
  16. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/base.py +20 -1
  17. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/cerebras.py +76 -76
  18. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/cohere.py +90 -90
  19. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/cstcloud.py +94 -94
  20. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/dashscope.py +92 -92
  21. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/dashscope_coding.py +94 -94
  22. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/deepseek.py +133 -133
  23. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/dmxapi.py +76 -76
  24. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/doubao.py +76 -76
  25. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/fireworks.py +76 -76
  26. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/google.py +99 -99
  27. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/grok.py +75 -75
  28. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/groq.py +76 -76
  29. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/huggingface.py +37 -37
  30. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/hyperbolic.py +76 -76
  31. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/infini.py +102 -102
  32. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/infini_coding.py +96 -96
  33. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/kimi.py +93 -93
  34. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/kimi_coding.py +96 -96
  35. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/longcat.py +76 -76
  36. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/mimo.py +76 -76
  37. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/mimo_plan.py +107 -107
  38. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/minimax.py +69 -69
  39. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/minimax_plan.py +94 -94
  40. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/mistral.py +76 -76
  41. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/models_registry.py +2962 -2962
  42. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/modelscope.py +120 -120
  43. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/nvidia.py +76 -76
  44. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/ocoolai.py +76 -76
  45. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/openai.py +101 -101
  46. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/opencode.py +89 -89
  47. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/opencode_zen.py +89 -89
  48. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/openrouter.py +91 -91
  49. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/perplexity.py +75 -75
  50. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/poe.py +76 -76
  51. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/ppio.py +76 -76
  52. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/replicate.py +37 -37
  53. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/siliconflow.py +93 -93
  54. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/stepfun.py +99 -99
  55. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/tencent_hunyuan.py +94 -94
  56. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/together.py +101 -101
  57. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/yi.py +69 -69
  58. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/zai.py +76 -76
  59. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/zhipu.py +99 -99
  60. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/zhipu_coding.py +96 -96
  61. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/ssrf.py +29 -2
  62. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/storage.py +15 -2
  63. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/tester.py +16 -13
  64. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/validator.py +16 -12
  65. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/web.py +1676 -1559
  66. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/webhook.py +6 -5
  67. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/pyproject.toml +2 -2
  68. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/tests/test_api_endpoints.py +193 -8
  69. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/tests/test_detector.py +71 -0
  70. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/tests/test_e2e.py +4 -1
  71. api_key_manager-3.0.0/tests/test_parser_supplement.py +216 -0
  72. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/tests/test_provider_detection.py +73 -14
  73. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/tests/test_security.py +3 -3
  74. api_key_manager-3.0.0/tests/test_ssrf_supplement.py +81 -0
  75. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/tests/test_tester.py +46 -20
  76. api_key_manager-3.0.0/tests/test_validator_supplement.py +196 -0
  77. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/tests/test_webhook.py +1 -1
  78. api_key_manager-2.2.0/key_manager/config.py +0 -61
  79. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/api_key_manager.egg-info/dependency_links.txt +0 -0
  80. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/api_key_manager.egg-info/entry_points.txt +0 -0
  81. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/api_key_manager.egg-info/requires.txt +0 -0
  82. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/api_key_manager.egg-info/top_level.txt +0 -0
  83. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/__main__.py +0 -0
  84. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/core.py +0 -0
  85. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/errors.py +0 -0
  86. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/i18n.py +0 -0
  87. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/model_capabilities.py +0 -0
  88. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/providers/__init__.py +0 -0
  89. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/proxy.py +0 -0
  90. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/py.typed +0 -0
  91. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/key_manager/url_override.py +0 -0
  92. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/setup.cfg +0 -0
  93. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/tests/test_base_check.py +0 -0
  94. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/tests/test_bug_fixes.py +0 -0
  95. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/tests/test_checker.py +0 -0
  96. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/tests/test_core.py +0 -0
  97. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/tests/test_errors.py +0 -0
  98. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/tests/test_i18n.py +0 -0
  99. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/tests/test_logger.py +0 -0
  100. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/tests/test_openapi.py +0 -0
  101. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/tests/test_parser.py +0 -0
  102. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/tests/test_providers.py +0 -0
  103. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/tests/test_proxy.py +0 -0
  104. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/tests/test_storage.py +0 -0
  105. {api_key_manager-2.2.0 → api_key_manager-3.0.0}/tests/test_validator.py +0 -0
  106. {api_key_manager-2.2.0 → 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.2.0
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,7 +45,7 @@ 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 客户端库
@@ -197,11 +197,13 @@ python web.py
197
197
  ### 安全防护
198
198
 
199
199
  - **路径遍历防护** - 导入端点验证路径在允许目录内
200
- - **SSRF 防护** - `custom_base_url` 验证域名白名单,阻止私有 IP,已接入 `check/single` 和 `balance` 端点
200
+ - **SSRF 防护** - `custom_base_url` 验证域名白名单,阻止私有 IP(含 IPv6),DNS 重绑定防护
201
201
  - **时序安全认证** - 使用 `hmac.compare_digest()` 防止时序攻击
202
- - **认证警告** - 未配置 API Key 时启动警告
202
+ - **强制认证** - 未配置 API Key 时自动生成随机密钥,拒绝未认证请求
203
203
  - **密钥掩码** - API 响应中只返回 `key_masked`,不暴露完整密钥
204
- - **Webhook 安全** - Webhook 端点使用正确的 API 方法,防止运行时错误
204
+ - **CORS 配置** - 跨域资源共享安全配置
205
+ - **Webhook 安全** - 使用 `secrets.token_hex` 生成不可预测的 webhook ID
206
+ - **存储安全** - 密钥文件权限加固 (0o600),AES-256-GCM 加密,PBKDF2 600,000 次迭代
205
207
 
206
208
  ### API 认证
207
209
 
@@ -718,6 +720,45 @@ const result = await client.checkSingleKey({ key: 'sk-xxx', provider: 'openai' }
718
720
 
719
721
  ## 更新日志
720
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
721
762
  ### v2.2.0 (2026-06-14)
722
763
 
723
764
  - **检测逻辑重构**: 添加三步检测逻辑(/v1/models → 并发测试)
@@ -12,7 +12,7 @@
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 客户端库
@@ -164,11 +164,13 @@ python web.py
164
164
  ### 安全防护
165
165
 
166
166
  - **路径遍历防护** - 导入端点验证路径在允许目录内
167
- - **SSRF 防护** - `custom_base_url` 验证域名白名单,阻止私有 IP,已接入 `check/single` 和 `balance` 端点
167
+ - **SSRF 防护** - `custom_base_url` 验证域名白名单,阻止私有 IP(含 IPv6),DNS 重绑定防护
168
168
  - **时序安全认证** - 使用 `hmac.compare_digest()` 防止时序攻击
169
- - **认证警告** - 未配置 API Key 时启动警告
169
+ - **强制认证** - 未配置 API Key 时自动生成随机密钥,拒绝未认证请求
170
170
  - **密钥掩码** - API 响应中只返回 `key_masked`,不暴露完整密钥
171
- - **Webhook 安全** - Webhook 端点使用正确的 API 方法,防止运行时错误
171
+ - **CORS 配置** - 跨域资源共享安全配置
172
+ - **Webhook 安全** - 使用 `secrets.token_hex` 生成不可预测的 webhook ID
173
+ - **存储安全** - 密钥文件权限加固 (0o600),AES-256-GCM 加密,PBKDF2 600,000 次迭代
172
174
 
173
175
  ### API 认证
174
176
 
@@ -685,6 +687,45 @@ const result = await client.checkSingleKey({ key: 'sk-xxx', provider: 'openai' }
685
687
 
686
688
  ## 更新日志
687
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
688
729
  ### v2.2.0 (2026-06-14)
689
730
 
690
731
  - **检测逻辑重构**: 添加三步检测逻辑(/v1/models → 并发测试)
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: api-key-manager
3
- Version: 2.2.0
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,7 +45,7 @@ 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 客户端库
@@ -197,11 +197,13 @@ python web.py
197
197
  ### 安全防护
198
198
 
199
199
  - **路径遍历防护** - 导入端点验证路径在允许目录内
200
- - **SSRF 防护** - `custom_base_url` 验证域名白名单,阻止私有 IP,已接入 `check/single` 和 `balance` 端点
200
+ - **SSRF 防护** - `custom_base_url` 验证域名白名单,阻止私有 IP(含 IPv6),DNS 重绑定防护
201
201
  - **时序安全认证** - 使用 `hmac.compare_digest()` 防止时序攻击
202
- - **认证警告** - 未配置 API Key 时启动警告
202
+ - **强制认证** - 未配置 API Key 时自动生成随机密钥,拒绝未认证请求
203
203
  - **密钥掩码** - API 响应中只返回 `key_masked`,不暴露完整密钥
204
- - **Webhook 安全** - Webhook 端点使用正确的 API 方法,防止运行时错误
204
+ - **CORS 配置** - 跨域资源共享安全配置
205
+ - **Webhook 安全** - 使用 `secrets.token_hex` 生成不可预测的 webhook ID
206
+ - **存储安全** - 密钥文件权限加固 (0o600),AES-256-GCM 加密,PBKDF2 600,000 次迭代
205
207
 
206
208
  ### API 认证
207
209
 
@@ -718,6 +720,45 @@ const result = await client.checkSingleKey({ key: 'sk-xxx', provider: 'openai' }
718
720
 
719
721
  ## 更新日志
720
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
721
762
  ### v2.2.0 (2026-06-14)
722
763
 
723
764
  - **检测逻辑重构**: 添加三步检测逻辑(/v1/models → 并发测试)
@@ -89,12 +89,15 @@ tests/test_i18n.py
89
89
  tests/test_logger.py
90
90
  tests/test_openapi.py
91
91
  tests/test_parser.py
92
+ tests/test_parser_supplement.py
92
93
  tests/test_provider_detection.py
93
94
  tests/test_providers.py
94
95
  tests/test_proxy.py
95
96
  tests/test_security.py
97
+ tests/test_ssrf_supplement.py
96
98
  tests/test_storage.py
97
99
  tests/test_tester.py
98
100
  tests/test_validator.py
101
+ tests/test_validator_supplement.py
99
102
  tests/test_web_fixes.py
100
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.2.0"
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}")