mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
fix: 过滤 Cloudflare CDN headers 以防止 API 安全检查
使用 Cloudflare 橙色云(CDN 代理模式)时,Cloudflare 会自动添加 CDN 相关的 headers (cf-*, x-forwarded-*, cdn-loop 等),这会触发上游 API 提供商的安全检查: 1. 已确认问题:88code API 检测到 CDN headers 后返回 403 Forbidden, 导致 Codex CLI 无法使用 2. 潜在风险:其他 API 提供商(OpenAI、Anthropic)可能也会因检测到 代理/CDN 特征而采取限制措施 创建统一的 headerFilter 工具类,在所有转发服务中过滤 Cloudflare CDN headers, 使转发请求伪装成正常的直接客户端请求。 1. 新增 src/utils/headerFilter.js - 统一的 CDN headers 过滤列表(13 个 Cloudflare headers) - 提供 filterForOpenAI() 和 filterForClaude() 方法 - 在现有过滤逻辑基础上添加 CDN header 过滤 2. 更新 src/services/openaiResponsesRelayService.js - 使用 filterForOpenAI() 替代内联的 _filterRequestHeaders() - 保持向后兼容性 3. 更新 src/services/claudeRelayService.js - 使用 filterForClaude() 替代 _filterClientHeaders() 实现 - 简化代码,移除重复的 header 列表定义 4. 修复 src/routes/openaiRoutes.js - 添加对 input 字段的类型检查(可以是数组或字符串) - 防止 "startsWith is not a function" 错误 x-real-ip, x-forwarded-for, x-forwarded-proto, x-forwarded-host, x-forwarded-port, x-accel-buffering, cf-ray, cf-connecting-ip, cf-ipcountry, cf-visitor, cf-request-id, cdn-loop, true-client-ip - ✅ Codex CLI 通过中转服务成功调用 88code API(之前返回 403) - ✅ 保留所有业务必需的 headers(conversation_id、session_id 等) - ✅ 移除所有 Cloudflare CDN 痕迹 - ✅ 保持橙色云的 DDoS 防护和 CDN 加速优势 - ✅ Docker 构建成功 1. 解决 88code 403 问题,Codex CLI 可正常使用 2. 降低因 CDN/代理特征被上游 API 识别的风险 3. 提升与各种 API 提供商的兼容性 4. 统一管理 CDN headers 过滤逻辑,便于维护
This commit is contained in:
133
src/utils/headerFilter.js
Normal file
133
src/utils/headerFilter.js
Normal file
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* 统一的 CDN Headers 过滤列表
|
||||
*
|
||||
* 用于各服务在原有过滤逻辑基础上,额外移除 Cloudflare CDN 和代理相关的 headers
|
||||
* 避免触发上游 API(如 88code)的安全检查
|
||||
*/
|
||||
|
||||
// Cloudflare CDN headers(橙色云代理模式会添加这些)
|
||||
const cdnHeaders = [
|
||||
'x-real-ip',
|
||||
'x-forwarded-for',
|
||||
'x-forwarded-proto',
|
||||
'x-forwarded-host',
|
||||
'x-forwarded-port',
|
||||
'x-accel-buffering',
|
||||
'cf-ray',
|
||||
'cf-connecting-ip',
|
||||
'cf-ipcountry',
|
||||
'cf-visitor',
|
||||
'cf-request-id',
|
||||
'cdn-loop',
|
||||
'true-client-ip'
|
||||
]
|
||||
|
||||
/**
|
||||
* 为 OpenAI/Responses API 过滤 headers
|
||||
* 在原有 skipHeaders 基础上添加 CDN headers
|
||||
*/
|
||||
function filterForOpenAI(headers) {
|
||||
const skipHeaders = [
|
||||
'host',
|
||||
'content-length',
|
||||
'authorization',
|
||||
'x-api-key',
|
||||
'x-cr-api-key',
|
||||
'connection',
|
||||
'upgrade',
|
||||
'sec-websocket-key',
|
||||
'sec-websocket-version',
|
||||
'sec-websocket-extensions',
|
||||
...cdnHeaders // 添加 CDN headers
|
||||
]
|
||||
|
||||
const filtered = {}
|
||||
for (const [key, value] of Object.entries(headers)) {
|
||||
if (!skipHeaders.includes(key.toLowerCase())) {
|
||||
filtered[key] = value
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
/**
|
||||
* 为 Claude/Anthropic API 过滤 headers
|
||||
* 在原有逻辑基础上添加 CDN headers 到敏感列表
|
||||
*/
|
||||
function filterForClaude(headers) {
|
||||
const sensitiveHeaders = [
|
||||
'content-type',
|
||||
'user-agent',
|
||||
'x-api-key',
|
||||
'authorization',
|
||||
'x-authorization',
|
||||
'host',
|
||||
'content-length',
|
||||
'connection',
|
||||
'proxy-authorization',
|
||||
'content-encoding',
|
||||
'transfer-encoding',
|
||||
...cdnHeaders // 添加 CDN headers
|
||||
]
|
||||
|
||||
const browserHeaders = [
|
||||
'origin',
|
||||
'referer',
|
||||
'sec-fetch-mode',
|
||||
'sec-fetch-site',
|
||||
'sec-fetch-dest',
|
||||
'sec-ch-ua',
|
||||
'sec-ch-ua-mobile',
|
||||
'sec-ch-ua-platform',
|
||||
'accept-language',
|
||||
'accept-encoding',
|
||||
'accept',
|
||||
'cache-control',
|
||||
'pragma',
|
||||
'anthropic-dangerous-direct-browser-access'
|
||||
]
|
||||
|
||||
const allowedHeaders = ['x-request-id', 'anthropic-version', 'anthropic-beta']
|
||||
|
||||
const filtered = {}
|
||||
Object.keys(headers || {}).forEach((key) => {
|
||||
const lowerKey = key.toLowerCase()
|
||||
if (allowedHeaders.includes(lowerKey)) {
|
||||
filtered[key] = headers[key]
|
||||
} else if (!sensitiveHeaders.includes(lowerKey) && !browserHeaders.includes(lowerKey)) {
|
||||
filtered[key] = headers[key]
|
||||
}
|
||||
})
|
||||
|
||||
return filtered
|
||||
}
|
||||
|
||||
/**
|
||||
* 为 Gemini API 过滤 headers(如果需要转发客户端 headers 时使用)
|
||||
* 目前 Gemini 服务不转发客户端 headers,仅提供此方法备用
|
||||
*/
|
||||
function filterForGemini(headers) {
|
||||
const skipHeaders = [
|
||||
'host',
|
||||
'content-length',
|
||||
'authorization',
|
||||
'x-api-key',
|
||||
'connection',
|
||||
...cdnHeaders // 添加 CDN headers
|
||||
]
|
||||
|
||||
const filtered = {}
|
||||
for (const [key, value] of Object.entries(headers)) {
|
||||
if (!skipHeaders.includes(key.toLowerCase())) {
|
||||
filtered[key] = value
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
cdnHeaders,
|
||||
filterForOpenAI,
|
||||
filterForClaude,
|
||||
filterForGemini
|
||||
}
|
||||
Reference in New Issue
Block a user