mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-16 15:43:13 +08:00
网关有若干位置会把客户端可控的字符串拼进上游请求的 URL path(Responses
子路径、Gemini 模型名)。此前这些字符串未经校验直接参与拼接,可能改变上游
请求的路径结构,使实际发出的请求与客户端意图不一致。
- 新增 internal/service/upstream_path_guard.go:路径片段闭集允许清单
(\w + `-` + `.`),拒绝空片段、纯点片段、超长片段与过深后缀
- /responses/*subpath 三条路由入口新增守卫,不可转发的子路径直接 404;
service 层同时保证不产出不合规后缀,拼接函数再兜底一层
- Gemini AI Studio 原先 5 处重复的 URL 拼接收敛为唯一构造点
buildGeminiAIStudioModelActionURL(校验模型片段 + action 白名单)
- Gemini native / GetModel handler 增加入口校验;ForwardAIStudioGET 逐片段校验
- Grok video 端点的 request_id 增加片段合规校验
合法子路径(/compact、/compact/detail、/{id}/cancel 形态)与既有模型名行为
不变,通配路由保留。
52 lines
1.9 KiB
Go
52 lines
1.9 KiB
Go
package service
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// geminiAIStudioActions 是允许出现在上游 URL 里的 action 集合,与 ForwardNative
|
|
// 的入站校验保持一致,同时避免 compat 侧把可变字符串直接拼进 path。
|
|
var geminiAIStudioActions = map[string]struct{}{
|
|
"generateContent": {},
|
|
"streamGenerateContent": {},
|
|
"countTokens": {},
|
|
}
|
|
|
|
// buildGeminiAIStudioModelActionURL 组装 AI Studio 的
|
|
// /v1beta/models/{model}:{action} 上游 URL。
|
|
//
|
|
// model 是客户端可控的(native 路由取自 URL 片段,compat 路由取自请求体的 model
|
|
// 字段,之后可能再经渠道映射),因此必须先过路径片段护栏才能拼进 path,
|
|
// 见 upstream_path_guard.go。新增 AI Studio 端点请一律走本函数。
|
|
func buildGeminiAIStudioModelActionURL(baseURL, model, action string, stream bool) (string, error) {
|
|
trimmedBase := strings.TrimRight(strings.TrimSpace(baseURL), "/")
|
|
if trimmedBase == "" {
|
|
return "", errors.New("gemini base url is required")
|
|
}
|
|
trimmedModel := strings.TrimSpace(model)
|
|
if trimmedModel == "" {
|
|
return "", errors.New("gemini model is required")
|
|
}
|
|
if err := validateUpstreamPathSegment("gemini model", trimmedModel); err != nil {
|
|
return "", err
|
|
}
|
|
trimmedAction := strings.TrimSpace(action)
|
|
if _, ok := geminiAIStudioActions[trimmedAction]; !ok {
|
|
return "", fmt.Errorf("unsupported gemini action: %s", trimmedAction)
|
|
}
|
|
|
|
fullURL := fmt.Sprintf("%s/v1beta/models/%s:%s", trimmedBase, trimmedModel, trimmedAction)
|
|
if stream {
|
|
fullURL += "?alt=sse"
|
|
}
|
|
return fullURL, nil
|
|
}
|
|
|
|
// IsSafeGeminiModelPathSegment 供 handler 层在解析出 URL 里的模型名后立刻校验,
|
|
// 让客户端拿到明确的 400,而不是等到构造上游请求时才报错。
|
|
func IsSafeGeminiModelPathSegment(model string) bool {
|
|
return isSafeUpstreamPathSegment(strings.TrimSpace(model))
|
|
}
|