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:
@@ -2,6 +2,7 @@ package logredact
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -19,6 +20,22 @@ var defaultSensitiveKeys = map[string]struct{}{
|
||||
"password": {},
|
||||
}
|
||||
|
||||
var defaultSensitiveKeyList = []string{
|
||||
"authorization_code",
|
||||
"code",
|
||||
"code_verifier",
|
||||
"access_token",
|
||||
"refresh_token",
|
||||
"id_token",
|
||||
"client_secret",
|
||||
"password",
|
||||
}
|
||||
|
||||
var (
|
||||
reGOCSPX = regexp.MustCompile(`GOCSPX-[0-9A-Za-z_-]{24,}`)
|
||||
reAIza = regexp.MustCompile(`AIza[0-9A-Za-z_-]{35}`)
|
||||
)
|
||||
|
||||
func RedactMap(input map[string]any, extraKeys ...string) map[string]any {
|
||||
if input == nil {
|
||||
return map[string]any{}
|
||||
@@ -48,6 +65,62 @@ func RedactJSON(raw []byte, extraKeys ...string) string {
|
||||
return string(encoded)
|
||||
}
|
||||
|
||||
// RedactText 对非结构化文本做轻量脱敏。
|
||||
//
|
||||
// 规则:
|
||||
// - 如果文本本身是 JSON,则按 RedactJSON 处理。
|
||||
// - 否则尝试对常见 key=value / key:"value" 片段做脱敏。
|
||||
//
|
||||
// 注意:该函数用于日志/错误信息兜底,不保证覆盖所有格式。
|
||||
func RedactText(input string, extraKeys ...string) string {
|
||||
input = strings.TrimSpace(input)
|
||||
if input == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
raw := []byte(input)
|
||||
if json.Valid(raw) {
|
||||
return RedactJSON(raw, extraKeys...)
|
||||
}
|
||||
|
||||
keyAlt := buildKeyAlternation(extraKeys)
|
||||
// JSON-like: "access_token":"..."
|
||||
reJSONLike := regexp.MustCompile(`(?i)("(?:` + keyAlt + `)"\s*:\s*")([^"]*)(")`)
|
||||
// Query-like: access_token=...
|
||||
reQueryLike := regexp.MustCompile(`(?i)\b((?:` + keyAlt + `))=([^&\s]+)`)
|
||||
// Plain: access_token: ... / access_token = ...
|
||||
rePlain := regexp.MustCompile(`(?i)\b((?:` + keyAlt + `))\b(\s*[:=]\s*)([^,\s]+)`)
|
||||
|
||||
out := input
|
||||
out = reGOCSPX.ReplaceAllString(out, "GOCSPX-***")
|
||||
out = reAIza.ReplaceAllString(out, "AIza***")
|
||||
out = reJSONLike.ReplaceAllString(out, `$1***$3`)
|
||||
out = reQueryLike.ReplaceAllString(out, `$1=***`)
|
||||
out = rePlain.ReplaceAllString(out, `$1$2***`)
|
||||
return out
|
||||
}
|
||||
|
||||
func buildKeyAlternation(extraKeys []string) string {
|
||||
seen := make(map[string]struct{}, len(defaultSensitiveKeyList)+len(extraKeys))
|
||||
keys := make([]string, 0, len(defaultSensitiveKeyList)+len(extraKeys))
|
||||
for _, k := range defaultSensitiveKeyList {
|
||||
seen[k] = struct{}{}
|
||||
keys = append(keys, regexp.QuoteMeta(k))
|
||||
}
|
||||
for _, k := range extraKeys {
|
||||
n := normalizeKey(k)
|
||||
if n == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[n]; ok {
|
||||
continue
|
||||
}
|
||||
seen[n] = struct{}{}
|
||||
keys = append(keys, regexp.QuoteMeta(n))
|
||||
}
|
||||
return strings.Join(keys, "|")
|
||||
}
|
||||
|
||||
func buildKeySet(extraKeys []string) map[string]struct{} {
|
||||
keys := make(map[string]struct{}, len(defaultSensitiveKeys)+len(extraKeys))
|
||||
for k := range defaultSensitiveKeys {
|
||||
|
||||
Reference in New Issue
Block a user