diff --git a/README.md b/README.md index b2ed81ec..e267fbcd 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,11 @@ > > 目标:让 `claude`(Claude Code CLI)与 Antigravity / Gemini 账户体系无缝对接,并提供可观测、可运维的稳定转发服务。 +> [!CAUTION] +> **安全更新通知**:v1.1.248 及以下版本存在严重的管理员认证绕过漏洞,攻击者可未授权访问管理面板。 +> +> **请立即更新到 v1.1.249+ 版本**,或迁移到新一代项目 **[CRS 2.0 (sub2api)](https://github.com/Wei-Shaw/sub2api)** +
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) diff --git a/README_EN.md b/README_EN.md index 856ecb90..e3e8cdbb 100644 --- a/README_EN.md +++ b/README_EN.md @@ -1,6 +1,12 @@ # Claude Relay Service (Antigravity Edition) -Maintained fork by **dadongwo**. + +> [!CAUTION] +> **Security Update**: v1.1.248 and below contain a critical admin authentication bypass vulnerability allowing unauthorized access to the admin panel. +> +> **Please update to v1.1.249+ immediately**, or migrate to the next-generation project **[CRS 2.0 (sub2api)](https://github.com/Wei-Shaw/sub2api)** + +
This fork focuses on: - Native compatibility for `claude` (Claude Code CLI) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..034e8480 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,21 @@ +# Security Policy + +## Supported Versions + +Use this section to tell people about which versions of your project are +currently being supported with security updates. + +| Version | Supported | +| ------- | ------------------ | +| 5.1.x | :white_check_mark: | +| 5.0.x | :x: | +| 4.0.x | :white_check_mark: | +| < 4.0 | :x: | + +## Reporting a Vulnerability + +Use this section to tell people how to report a vulnerability. + +Tell them where to go, how often they can expect to get an update on a +reported vulnerability, what to expect if the vulnerability is accepted or +declined, etc. diff --git a/VERSION b/VERSION index 9c6cacb8..0f5fe772 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.241 +1.1.252 diff --git a/src/middleware/auth.js b/src/middleware/auth.js index 8cccee0c..1883a2a0 100644 --- a/src/middleware/auth.js +++ b/src/middleware/auth.js @@ -1434,7 +1434,6 @@ const authenticateAdmin = async (req, res, next) => { // 设置管理员信息(只包含必要信息) req.admin = { - id: adminSession.adminId || 'admin', username: adminSession.username, sessionId: token, loginTime: adminSession.loginTime @@ -1567,17 +1566,25 @@ const authenticateUserOrAdmin = async (req, res, next) => { try { const adminSession = await redis.getSession(adminToken) if (adminSession && Object.keys(adminSession).length > 0) { - req.admin = { - id: adminSession.adminId || 'admin', - username: adminSession.username, - sessionId: adminToken, - loginTime: adminSession.loginTime - } - req.userType = 'admin' + // 🔒 安全修复:验证会话必须字段(与 authenticateAdmin 保持一致) + if (!adminSession.username || !adminSession.loginTime) { + logger.security( + `🔒 Corrupted admin session in authenticateUserOrAdmin from ${req.ip || 'unknown'} - missing required fields (username: ${!!adminSession.username}, loginTime: ${!!adminSession.loginTime})` + ) + await redis.deleteSession(adminToken) // 清理无效/伪造的会话 + // 不返回 401,继续尝试用户认证 + } else { + req.admin = { + username: adminSession.username, + sessionId: adminToken, + loginTime: adminSession.loginTime + } + req.userType = 'admin' - const authDuration = Date.now() - startTime - logger.security(`🔐 Admin authenticated: ${adminSession.username} in ${authDuration}ms`) - return next() + const authDuration = Date.now() - startTime + logger.security(`🔐 Admin authenticated: ${adminSession.username} in ${authDuration}ms`) + return next() + } } } catch (error) { logger.debug('Admin authentication failed, trying user authentication:', error.message) diff --git a/src/routes/api.js b/src/routes/api.js index 6ec81cbd..4b983d64 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -205,18 +205,18 @@ async function handleMessagesRequest(req, res) { const isStream = req.body.stream === true // 临时修复新版本客户端,删除context_management字段,避免报错 - // if (req.body.context_management) { - // delete req.body.context_management - // } + if (req.body.context_management) { + delete req.body.context_management + } // 遍历tools数组,删除input_examples字段 - // if (req.body.tools && Array.isArray(req.body.tools)) { - // req.body.tools.forEach((tool) => { - // if (tool && typeof tool === 'object' && tool.input_examples) { - // delete tool.input_examples - // } - // }) - // } + if (req.body.tools && Array.isArray(req.body.tools)) { + req.body.tools.forEach((tool) => { + if (tool && typeof tool === 'object' && tool.input_examples) { + delete tool.input_examples + } + }) + } logger.api( `🚀 Processing ${isStream ? 'stream' : 'non-stream'} request for key: ${req.apiKey.name}` diff --git a/src/routes/openaiClaudeRoutes.js b/src/routes/openaiClaudeRoutes.js index 2bb7cc09..35314e4a 100644 --- a/src/routes/openaiClaudeRoutes.js +++ b/src/routes/openaiClaudeRoutes.js @@ -402,16 +402,29 @@ async function handleChatCompletion(req, res, apiKeyData) { const duration = Date.now() - startTime logger.info(`✅ OpenAI-Claude request completed in ${duration}ms`) } catch (error) { - logger.error('❌ OpenAI-Claude request error:', error) + // 客户端主动断开连接是正常情况,使用 INFO 级别 + if (error.message === 'Client disconnected') { + logger.info('🔌 OpenAI-Claude stream ended: Client disconnected') + } else { + logger.error('❌ OpenAI-Claude request error:', error) + } - const status = error.status || 500 - res.status(status).json({ - error: { - message: error.message || 'Internal server error', - type: 'server_error', - code: 'internal_error' + // 检查响应是否已发送(流式响应场景),避免 ERR_HTTP_HEADERS_SENT + if (!res.headersSent) { + // 客户端断开使用 499 状态码 (Client Closed Request) + if (error.message === 'Client disconnected') { + res.status(499).end() + } else { + const status = error.status || 500 + res.status(status).json({ + error: { + message: error.message || 'Internal server error', + type: 'server_error', + code: 'internal_error' + } + }) } - }) + } } finally { // 清理资源 if (abortController) { diff --git a/src/routes/openaiGeminiRoutes.js b/src/routes/openaiGeminiRoutes.js index a2678e3f..9b6b5bb8 100644 --- a/src/routes/openaiGeminiRoutes.js +++ b/src/routes/openaiGeminiRoutes.js @@ -673,17 +673,24 @@ router.post('/v1/chat/completions', authenticateApiKey, async (req, res) => { } } - // 返回 OpenAI 格式的错误响应 - const status = error.status || 500 - const errorResponse = { - error: error.error || { - message: error.message || 'Internal server error', - type: 'server_error', - code: 'internal_error' + // 检查响应是否已发送(流式响应场景),避免 ERR_HTTP_HEADERS_SENT + if (!res.headersSent) { + // 客户端断开使用 499 状态码 (Client Closed Request) + if (error.message === 'Client disconnected') { + res.status(499).end() + } else { + // 返回 OpenAI 格式的错误响应 + const status = error.status || 500 + const errorResponse = { + error: error.error || { + message: error.message || 'Internal server error', + type: 'server_error', + code: 'internal_error' + } + } + res.status(status).json(errorResponse) } } - - res.status(status).json(errorResponse) } finally { // 清理资源 if (abortController) {