Merge branch 'main' into antigravity

This commit is contained in:
Wesley Liddick
2026-01-07 03:49:14 -05:00
committed by GitHub
8 changed files with 99 additions and 40 deletions

View File

@@ -4,6 +4,11 @@
> >
> 目标:让 `claude`Claude Code CLI与 Antigravity / Gemini 账户体系无缝对接,并提供可观测、可运维的稳定转发服务。 > 目标:让 `claude`Claude Code CLI与 Antigravity / Gemini 账户体系无缝对接,并提供可观测、可运维的稳定转发服务。
> [!CAUTION]
> **安全更新通知**v1.1.248 及以下版本存在严重的管理员认证绕过漏洞,攻击者可未授权访问管理面板。
>
> **请立即更新到 v1.1.249+ 版本**,或迁移到新一代项目 **[CRS 2.0 (sub2api)](https://github.com/Wei-Shaw/sub2api)**
<div align="center"> <div align="center">
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

View File

@@ -1,6 +1,12 @@
# Claude Relay Service (Antigravity Edition) # 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)**
<div align="center">
This fork focuses on: This fork focuses on:
- Native compatibility for `claude` (Claude Code CLI) - Native compatibility for `claude` (Claude Code CLI)

21
SECURITY.md Normal file
View File

@@ -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.

View File

@@ -1 +1 @@
1.1.241 1.1.252

View File

@@ -1434,7 +1434,6 @@ 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
@@ -1567,8 +1566,15 @@ 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
@@ -1579,6 +1585,7 @@ 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)
} }

View File

@@ -205,18 +205,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}`

View File

@@ -402,8 +402,19 @@ 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: {
@@ -412,6 +423,8 @@ async function handleChatCompletion(req, res, apiKeyData) {
code: 'internal_error' code: 'internal_error'
} }
}) })
}
}
} finally { } finally {
// 清理资源 // 清理资源
if (abortController) { if (abortController) {

View File

@@ -673,6 +673,12 @@ 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 = {
@@ -682,8 +688,9 @@ 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) {