mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:38:14 +00:00
Merge pull request #752 from IanShaw027/fix/filter-cloudflare-cdn-headers
fix: 过滤 Cloudflare CDN headers 以防止 API 安全检查
This commit is contained in:
@@ -3,6 +3,7 @@ const zlib = require('zlib')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const ProxyHelper = require('../utils/proxyHelper')
|
||||
const { filterForClaude } = require('../utils/headerFilter')
|
||||
const claudeAccountService = require('./claudeAccountService')
|
||||
const unifiedClaudeScheduler = require('./unifiedClaudeScheduler')
|
||||
const sessionHelper = require('../utils/sessionHelper')
|
||||
@@ -877,62 +878,9 @@ class ClaudeRelayService {
|
||||
|
||||
// 🔧 过滤客户端请求头
|
||||
_filterClientHeaders(clientHeaders) {
|
||||
// 需要移除的敏感 headers
|
||||
const sensitiveHeaders = [
|
||||
'content-type',
|
||||
'user-agent',
|
||||
'x-api-key',
|
||||
'authorization',
|
||||
'x-authorization',
|
||||
'host',
|
||||
'content-length',
|
||||
'connection',
|
||||
'proxy-authorization',
|
||||
'content-encoding',
|
||||
'transfer-encoding'
|
||||
]
|
||||
|
||||
// 🆕 需要移除的浏览器相关 headers(避免CORS问题)
|
||||
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' // 这个头可能触发CORS检查
|
||||
]
|
||||
|
||||
// 应该保留的 headers(用于会话一致性和追踪)
|
||||
const allowedHeaders = [
|
||||
'x-request-id',
|
||||
'anthropic-version', // 保留API版本
|
||||
'anthropic-beta' // 保留beta功能
|
||||
]
|
||||
|
||||
const filteredHeaders = {}
|
||||
|
||||
// 转发客户端的非敏感 headers
|
||||
Object.keys(clientHeaders || {}).forEach((key) => {
|
||||
const lowerKey = key.toLowerCase()
|
||||
// 如果在允许列表中,直接保留
|
||||
if (allowedHeaders.includes(lowerKey)) {
|
||||
filteredHeaders[key] = clientHeaders[key]
|
||||
}
|
||||
// 如果不在敏感列表和浏览器列表中,也保留
|
||||
else if (!sensitiveHeaders.includes(lowerKey) && !browserHeaders.includes(lowerKey)) {
|
||||
filteredHeaders[key] = clientHeaders[key]
|
||||
}
|
||||
})
|
||||
|
||||
return filteredHeaders
|
||||
// 使用统一的 headerFilter 工具类 - 移除 CDN、浏览器和代理相关 headers
|
||||
// 同时伪装成正常的直接客户端请求,避免触发上游 API 的安全检查
|
||||
return filterForClaude(clientHeaders)
|
||||
}
|
||||
|
||||
_applyRequestIdentityTransform(body, headers, context = {}) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const axios = require('axios')
|
||||
const ProxyHelper = require('../utils/proxyHelper')
|
||||
const logger = require('../utils/logger')
|
||||
const { filterForOpenAI } = require('../utils/headerFilter')
|
||||
const openaiResponsesAccountService = require('./openaiResponsesAccountService')
|
||||
const apiKeyService = require('./apiKeyService')
|
||||
const unifiedOpenAIScheduler = require('./unifiedOpenAIScheduler')
|
||||
@@ -73,9 +74,9 @@ class OpenAIResponsesRelayService {
|
||||
const targetUrl = `${fullAccount.baseApi}${req.path}`
|
||||
logger.info(`🎯 Forwarding to: ${targetUrl}`)
|
||||
|
||||
// 构建请求头
|
||||
// 构建请求头 - 使用统一的 headerFilter 移除 CDN headers
|
||||
const headers = {
|
||||
...this._filterRequestHeaders(req.headers),
|
||||
...filterForOpenAI(req.headers),
|
||||
Authorization: `Bearer ${fullAccount.apiKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
@@ -810,29 +811,10 @@ class OpenAIResponsesRelayService {
|
||||
return { resetsInSeconds, errorData }
|
||||
}
|
||||
|
||||
// 过滤请求头
|
||||
// 过滤请求头 - 已迁移到 headerFilter 工具类
|
||||
// 此方法保留用于向后兼容,实际使用 filterForOpenAI()
|
||||
_filterRequestHeaders(headers) {
|
||||
const filtered = {}
|
||||
const skipHeaders = [
|
||||
'host',
|
||||
'content-length',
|
||||
'authorization',
|
||||
'x-api-key',
|
||||
'x-cr-api-key',
|
||||
'connection',
|
||||
'upgrade',
|
||||
'sec-websocket-key',
|
||||
'sec-websocket-version',
|
||||
'sec-websocket-extensions'
|
||||
]
|
||||
|
||||
for (const [key, value] of Object.entries(headers)) {
|
||||
if (!skipHeaders.includes(key.toLowerCase())) {
|
||||
filtered[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return filtered
|
||||
return filterForOpenAI(headers)
|
||||
}
|
||||
|
||||
// 估算费用(简化版本,实际应该根据不同的定价模型)
|
||||
|
||||
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