fix: 修复代码审核发现的安全和质量问题
安全修复(P0): - 移除硬编码的 OAuth client_secret(Antigravity、Gemini CLI), 改为通过环境变量注入(ANTIGRAVITY_OAUTH_CLIENT_SECRET、 GEMINI_CLI_OAUTH_CLIENT_SECRET) - 新增 logredact.RedactText() 对非结构化文本做敏感信息脱敏, 覆盖 GOCSPX-*/AIza* 令牌和常见 key=value 模式 - 日志中不再打印 org_uuid、account_uuid、email_address 等敏感值 安全修复(P1): - URL 验证增强:新增 ValidateHTTPURL 统一入口,支持 allowlist 和 私网地址阻断(localhost/内网 IP) - 代理回退安全:代理初始化失败时默认阻止直连回退,防止 IP 泄露, 可通过 security.proxy_fallback.allow_direct_on_error 显式开启 - Gemini OAuth 配置校验:client_id 与 client_secret 必须同时 设置或同时留空 其他改进: - 新增 tools/secret_scan.py 密钥扫描工具和 Makefile secret-scan 目标 - 更新所有 docker-compose 和部署配置,传递 OAuth secret 环境变量 - google_one OAuth 类型使用固定 redirectURI,与 code_assist 对齐 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,58 @@ type ValidationOptions struct {
|
||||
AllowPrivate bool
|
||||
}
|
||||
|
||||
// ValidateHTTPURL validates an outbound HTTP/HTTPS URL.
|
||||
//
|
||||
// It provides a single validation entry point that supports:
|
||||
// - scheme 校验(https 或可选允许 http)
|
||||
// - 可选 allowlist(支持 *.example.com 通配)
|
||||
// - allow_private_hosts 策略(阻断 localhost/私网字面量 IP)
|
||||
//
|
||||
// 注意:DNS Rebinding 防护(解析后 IP 校验)应在实际发起请求时执行,避免 TOCTOU。
|
||||
func ValidateHTTPURL(raw string, allowInsecureHTTP bool, opts ValidationOptions) (string, error) {
|
||||
trimmed := strings.TrimSpace(raw)
|
||||
if trimmed == "" {
|
||||
return "", errors.New("url is required")
|
||||
}
|
||||
|
||||
parsed, err := url.Parse(trimmed)
|
||||
if err != nil || parsed.Scheme == "" || parsed.Host == "" {
|
||||
return "", fmt.Errorf("invalid url: %s", trimmed)
|
||||
}
|
||||
|
||||
scheme := strings.ToLower(parsed.Scheme)
|
||||
if scheme != "https" && (!allowInsecureHTTP || scheme != "http") {
|
||||
return "", fmt.Errorf("invalid url scheme: %s", parsed.Scheme)
|
||||
}
|
||||
|
||||
host := strings.ToLower(strings.TrimSpace(parsed.Hostname()))
|
||||
if host == "" {
|
||||
return "", errors.New("invalid host")
|
||||
}
|
||||
if !opts.AllowPrivate && isBlockedHost(host) {
|
||||
return "", fmt.Errorf("host is not allowed: %s", host)
|
||||
}
|
||||
|
||||
if port := parsed.Port(); port != "" {
|
||||
num, err := strconv.Atoi(port)
|
||||
if err != nil || num <= 0 || num > 65535 {
|
||||
return "", fmt.Errorf("invalid port: %s", port)
|
||||
}
|
||||
}
|
||||
|
||||
allowlist := normalizeAllowlist(opts.AllowedHosts)
|
||||
if opts.RequireAllowlist && len(allowlist) == 0 {
|
||||
return "", errors.New("allowlist is not configured")
|
||||
}
|
||||
if len(allowlist) > 0 && !isAllowedHost(host, allowlist) {
|
||||
return "", fmt.Errorf("host is not allowed: %s", host)
|
||||
}
|
||||
|
||||
parsed.Path = strings.TrimRight(parsed.Path, "/")
|
||||
parsed.RawPath = ""
|
||||
return strings.TrimRight(parsed.String(), "/"), nil
|
||||
}
|
||||
|
||||
func ValidateURLFormat(raw string, allowInsecureHTTP bool) (string, error) {
|
||||
// 最小格式校验:仅保证 URL 可解析且 scheme 合规,不做白名单/私网/SSRF 校验
|
||||
trimmed := strings.TrimSpace(raw)
|
||||
@@ -50,38 +102,7 @@ func ValidateURLFormat(raw string, allowInsecureHTTP bool) (string, error) {
|
||||
}
|
||||
|
||||
func ValidateHTTPSURL(raw string, opts ValidationOptions) (string, error) {
|
||||
trimmed := strings.TrimSpace(raw)
|
||||
if trimmed == "" {
|
||||
return "", errors.New("url is required")
|
||||
}
|
||||
|
||||
parsed, err := url.Parse(trimmed)
|
||||
if err != nil || parsed.Scheme == "" || parsed.Host == "" {
|
||||
return "", fmt.Errorf("invalid url: %s", trimmed)
|
||||
}
|
||||
if !strings.EqualFold(parsed.Scheme, "https") {
|
||||
return "", fmt.Errorf("invalid url scheme: %s", parsed.Scheme)
|
||||
}
|
||||
|
||||
host := strings.ToLower(strings.TrimSpace(parsed.Hostname()))
|
||||
if host == "" {
|
||||
return "", errors.New("invalid host")
|
||||
}
|
||||
if !opts.AllowPrivate && isBlockedHost(host) {
|
||||
return "", fmt.Errorf("host is not allowed: %s", host)
|
||||
}
|
||||
|
||||
allowlist := normalizeAllowlist(opts.AllowedHosts)
|
||||
if opts.RequireAllowlist && len(allowlist) == 0 {
|
||||
return "", errors.New("allowlist is not configured")
|
||||
}
|
||||
if len(allowlist) > 0 && !isAllowedHost(host, allowlist) {
|
||||
return "", fmt.Errorf("host is not allowed: %s", host)
|
||||
}
|
||||
|
||||
parsed.Path = strings.TrimRight(parsed.Path, "/")
|
||||
parsed.RawPath = ""
|
||||
return strings.TrimRight(parsed.String(), "/"), nil
|
||||
return ValidateHTTPURL(raw, false, opts)
|
||||
}
|
||||
|
||||
// ValidateResolvedIP 验证 DNS 解析后的 IP 地址是否安全
|
||||
|
||||
Reference in New Issue
Block a user