fix: 双模型审查 Critical 修复
Some checks failed
CI / test (push) Failing after 1m32s
CI / golangci-lint (push) Failing after 32s
Security Scan / backend-security (push) Failing after 32s
Security Scan / frontend-security (push) Failing after 1m32s

1. Sora session_key 按 accountID 隔离(消除跨账号指纹关联)
2. 有 per-account 代理的 Sora 账号跳过 sidecar(保持代理 IP)
3. 请求体用 base64 编码传输(防止二进制数据损坏)
4. Node.js 代理 Body 用 GetBody 安全复制(修复重试时 Body 枯竭)
This commit is contained in:
win 2026-03-22 12:04:31 +08:00
parent 2b0192e704
commit a16db8e367
3 changed files with 38 additions and 11 deletions

View File

@ -286,7 +286,12 @@ func (s *httpUpstreamService) doViaNodeTLSProxy(req *http.Request, proxyURL stri
// 克隆请求,避免修改原始 req重试时需要原始 URL
proxyReq := req.Clone(req.Context())
proxyReq.Body = req.Body // Clone 不复制 Body
// 安全复制 Body优先用 GetBody 工厂方法
if req.GetBody != nil {
proxyReq.Body, _ = req.GetBody()
} else {
proxyReq.Body = req.Body
}
// 保存原始目标主机,通过自定义头传给 Node.js 代理
originalHost := req.URL.Host

View File

@ -3,6 +3,7 @@ package service
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
@ -1026,11 +1027,19 @@ func (c *SoraSDKClient) debugLogf(format string, args ...any) {
}
// doSoraHTTP 执行 Sora HTTP 请求,优先走 curl_cffi sidecarChrome TLS 指纹绕过 Cloudflare
// 如果 sidecar 未启用或不可用,回退到 httpUpstream.Do() / http.DefaultClient
// 如果 sidecar 未启用、账号有独立代理、 sidecar 不可用,回退到 httpUpstream.Do()
func (c *SoraSDKClient) doSoraHTTP(req *http.Request, proxyURL string, accountID int64, accountConcurrency int) (*http.Response, error) {
// 如果账号配置了独立代理,跳过 sidecarsidecar 不支持 per-account 代理)
if proxyURL != "" {
if c.httpUpstream != nil {
return c.httpUpstream.Do(req, proxyURL, accountID, accountConcurrency)
}
return http.DefaultClient.Do(req)
}
// 检查 sidecar 是否启用
if c.cfg != nil && c.cfg.Sora.Client.CurlCFFISidecar.Enabled && c.cfg.Sora.Client.CurlCFFISidecar.BaseURL != "" {
resp, err := c.doViaSidecar(req)
resp, err := c.doViaSidecar(req, accountID)
if err == nil {
return resp, nil
}
@ -1046,17 +1055,17 @@ func (c *SoraSDKClient) doSoraHTTP(req *http.Request, proxyURL string, accountID
}
// doViaSidecar 通过 curl_cffi sidecar 发送请求Chrome TLS 指纹)
func (c *SoraSDKClient) doViaSidecar(originalReq *http.Request) (*http.Response, error) {
func (c *SoraSDKClient) doViaSidecar(originalReq *http.Request, accountID int64) (*http.Response, error) {
sidecarURL := strings.TrimRight(c.cfg.Sora.Client.CurlCFFISidecar.BaseURL, "/") + "/proxy"
// 读取原始请求体
var bodyStr string
// 读取原始请求体,用 base64 编码(安全传输二进制数据)
var bodyB64 string
if originalReq.Body != nil {
bodyBytes, err := io.ReadAll(originalReq.Body)
if err != nil {
return nil, fmt.Errorf("read request body: %w", err)
}
bodyStr = string(bodyBytes)
bodyB64 = base64.StdEncoding.EncodeToString(bodyBytes)
// 恢复 body 以备回退
originalReq.Body = io.NopCloser(bytes.NewReader(bodyBytes))
}
@ -1073,8 +1082,9 @@ func (c *SoraSDKClient) doViaSidecar(originalReq *http.Request) (*http.Response,
"url": originalReq.URL.String(),
"method": originalReq.Method,
"headers": headers,
"body": bodyStr,
"session_key": fmt.Sprintf("account_%d", 0), // 简单的 session key
"body": bodyB64,
"is_base64": true,
"session_key": fmt.Sprintf("account_%d", accountID), // 每账号独立会话
}
payloadBytes, err := json.Marshal(payload)

View File

@ -4,7 +4,7 @@ sub2api 通过 HTTP 调用此服务转发 Sora 请求
"""
from flask import Flask, request, Response
from curl_cffi import requests as cffi_requests
import json, os, time, threading
import json, os, time, threading, base64
app = Flask(__name__)
@ -54,18 +54,30 @@ def proxy():
method = data.get("method", "GET").upper()
headers = data.get("headers", {})
body = data.get("body")
is_base64 = data.get("is_base64", False)
session_key = data.get("session_key", "default")
if not url:
return json.dumps({"error": "url required"}), 400
# 解码 base64 编码的请求体(安全传输二进制数据)
req_data = None
if body:
if is_base64:
try:
req_data = base64.b64decode(body)
except Exception:
req_data = body.encode("utf-8") if isinstance(body, str) else body
else:
req_data = body.encode("utf-8") if isinstance(body, str) else body
try:
sess = get_session(session_key)
resp = sess.request(
method=method,
url=url,
headers=headers,
data=body.encode("utf-8") if isinstance(body, str) else body,
data=req_data,
timeout=TIMEOUT,
allow_redirects=True,
)