mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-24 08:01:48 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e561387e81 |
@@ -1,10 +1,5 @@
|
|||||||
# Claude Relay Service
|
# Claude Relay Service
|
||||||
|
|
||||||
> [!CAUTION]
|
|
||||||
> **安全更新通知**:v1.1.248 及以下版本存在严重的管理员认证绕过漏洞,攻击者可未授权访问管理面板。
|
|
||||||
>
|
|
||||||
> **请立即更新到 v1.1.249+ 版本**,或迁移到新一代项目 **[CRS 2.0 (sub2api)](https://github.com/Wei-Shaw/sub2api)**
|
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
[](https://opensource.org/licenses/MIT)
|
[](https://opensource.org/licenses/MIT)
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
# Claude Relay Service
|
# Claude Relay Service
|
||||||
|
|
||||||
> [!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)**
|
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
[](https://opensource.org/licenses/MIT)
|
[](https://opensource.org/licenses/MIT)
|
||||||
|
|||||||
21
SECURITY.md
21
SECURITY.md
@@ -1,21 +0,0 @@
|
|||||||
# 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.
|
|
||||||
@@ -1434,6 +1434,7 @@ const authenticateAdmin = async (req, res, next) => {
|
|||||||
|
|
||||||
// 设置管理员信息(只包含必要信息)
|
// 设置管理员信息(只包含必要信息)
|
||||||
req.admin = {
|
req.admin = {
|
||||||
|
id: adminSession.adminId || 'admin',
|
||||||
username: adminSession.username,
|
username: adminSession.username,
|
||||||
sessionId: token,
|
sessionId: token,
|
||||||
loginTime: adminSession.loginTime
|
loginTime: adminSession.loginTime
|
||||||
@@ -1566,15 +1567,8 @@ const authenticateUserOrAdmin = async (req, res, next) => {
|
|||||||
try {
|
try {
|
||||||
const adminSession = await redis.getSession(adminToken)
|
const adminSession = await redis.getSession(adminToken)
|
||||||
if (adminSession && Object.keys(adminSession).length > 0) {
|
if (adminSession && Object.keys(adminSession).length > 0) {
|
||||||
// 🔒 安全修复:验证会话必须字段(与 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 = {
|
req.admin = {
|
||||||
|
id: adminSession.adminId || 'admin',
|
||||||
username: adminSession.username,
|
username: adminSession.username,
|
||||||
sessionId: adminToken,
|
sessionId: adminToken,
|
||||||
loginTime: adminSession.loginTime
|
loginTime: adminSession.loginTime
|
||||||
@@ -1585,7 +1579,6 @@ const authenticateUserOrAdmin = async (req, res, next) => {
|
|||||||
logger.security(`🔐 Admin authenticated: ${adminSession.username} in ${authDuration}ms`)
|
logger.security(`🔐 Admin authenticated: ${adminSession.username} in ${authDuration}ms`)
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.debug('Admin authentication failed, trying user authentication:', error.message)
|
logger.debug('Admin authentication failed, trying user authentication:', error.message)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -179,18 +179,18 @@ async function handleMessagesRequest(req, res) {
|
|||||||
const isStream = req.body.stream === true
|
const isStream = req.body.stream === true
|
||||||
|
|
||||||
// 临时修复新版本客户端,删除context_management字段,避免报错
|
// 临时修复新版本客户端,删除context_management字段,避免报错
|
||||||
if (req.body.context_management) {
|
// if (req.body.context_management) {
|
||||||
delete req.body.context_management
|
// delete req.body.context_management
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 遍历tools数组,删除input_examples字段
|
// 遍历tools数组,删除input_examples字段
|
||||||
if (req.body.tools && Array.isArray(req.body.tools)) {
|
// if (req.body.tools && Array.isArray(req.body.tools)) {
|
||||||
req.body.tools.forEach((tool) => {
|
// req.body.tools.forEach((tool) => {
|
||||||
if (tool && typeof tool === 'object' && tool.input_examples) {
|
// if (tool && typeof tool === 'object' && tool.input_examples) {
|
||||||
delete tool.input_examples
|
// delete tool.input_examples
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
logger.api(
|
logger.api(
|
||||||
`🚀 Processing ${isStream ? 'stream' : 'non-stream'} request for key: ${req.apiKey.name}`
|
`🚀 Processing ${isStream ? 'stream' : 'non-stream'} request for key: ${req.apiKey.name}`
|
||||||
|
|||||||
@@ -402,19 +402,8 @@ async function handleChatCompletion(req, res, apiKeyData) {
|
|||||||
const duration = Date.now() - startTime
|
const duration = Date.now() - startTime
|
||||||
logger.info(`✅ OpenAI-Claude request completed in ${duration}ms`)
|
logger.info(`✅ OpenAI-Claude request completed in ${duration}ms`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 客户端主动断开连接是正常情况,使用 INFO 级别
|
|
||||||
if (error.message === 'Client disconnected') {
|
|
||||||
logger.info('🔌 OpenAI-Claude stream ended: Client disconnected')
|
|
||||||
} else {
|
|
||||||
logger.error('❌ OpenAI-Claude request error:', error)
|
logger.error('❌ OpenAI-Claude request error:', 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
|
const status = error.status || 500
|
||||||
res.status(status).json({
|
res.status(status).json({
|
||||||
error: {
|
error: {
|
||||||
@@ -423,8 +412,6 @@ async function handleChatCompletion(req, res, apiKeyData) {
|
|||||||
code: 'internal_error'
|
code: 'internal_error'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
// 清理资源
|
// 清理资源
|
||||||
if (abortController) {
|
if (abortController) {
|
||||||
|
|||||||
@@ -604,12 +604,7 @@ router.post('/v1/chat/completions', authenticateApiKey, async (req, res) => {
|
|||||||
const duration = Date.now() - startTime
|
const duration = Date.now() - startTime
|
||||||
logger.info(`OpenAI-Gemini request completed in ${duration}ms`)
|
logger.info(`OpenAI-Gemini request completed in ${duration}ms`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 客户端主动断开连接是正常情况,使用 INFO 级别
|
|
||||||
if (error.message === 'Client disconnected') {
|
|
||||||
logger.info('🔌 OpenAI-Gemini stream ended: Client disconnected')
|
|
||||||
} else {
|
|
||||||
logger.error('OpenAI-Gemini request error:', error)
|
logger.error('OpenAI-Gemini request error:', error)
|
||||||
}
|
|
||||||
|
|
||||||
// 处理速率限制
|
// 处理速率限制
|
||||||
if (error.status === 429) {
|
if (error.status === 429) {
|
||||||
@@ -618,12 +613,6 @@ router.post('/v1/chat/completions', authenticateApiKey, async (req, res) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查响应是否已发送(流式响应场景),避免 ERR_HTTP_HEADERS_SENT
|
|
||||||
if (!res.headersSent) {
|
|
||||||
// 客户端断开使用 499 状态码 (Client Closed Request)
|
|
||||||
if (error.message === 'Client disconnected') {
|
|
||||||
res.status(499).end()
|
|
||||||
} else {
|
|
||||||
// 返回 OpenAI 格式的错误响应
|
// 返回 OpenAI 格式的错误响应
|
||||||
const status = error.status || 500
|
const status = error.status || 500
|
||||||
const errorResponse = {
|
const errorResponse = {
|
||||||
@@ -633,9 +622,8 @@ router.post('/v1/chat/completions', authenticateApiKey, async (req, res) => {
|
|||||||
code: 'internal_error'
|
code: 'internal_error'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res.status(status).json(errorResponse)
|
res.status(status).json(errorResponse)
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
// 清理资源
|
// 清理资源
|
||||||
if (abortController) {
|
if (abortController) {
|
||||||
|
|||||||
Reference in New Issue
Block a user