Files
sub2api/backend/internal/service/antigravity_upstream_error_sanitize.go
T
William DrakeandClaude Opus 5.5 d359196adc fix(antigravity): sanitize upstream error body before returning to client
The antigravity Gemini forward path returned the raw upstream Google error
body to end users, which can contain consumer project numbers, GCP project
IDs and service account emails. Return a Gemini-style error body with only
code/status and a scrubbed message; raw body stays in ops logging.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 12:23:13 -04:00

98 lines
2.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"encoding/json"
"net/http"
"regexp"
"strings"
)
var (
antigravityProjectRefRegex = regexp.MustCompile(`(?i)\bprojects/[a-z0-9][a-z0-9._:-]*`)
antigravityEmailRegex = regexp.MustCompile(`[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}`)
antigravityConsumerRegex = regexp.MustCompile(`(?i)\b(consumer|project(?:[ _-]?(?:id|number))?)(\s*[:=]?\s*['"]?)[0-9]{6,}`)
)
// sanitizeAntigravityErrorText 清除上游错误文本中的 GCP 项目号/项目 ID、服务账号邮箱及敏感查询参数。
func sanitizeAntigravityErrorText(msg string) string {
if msg == "" {
return msg
}
msg = sanitizeUpstreamErrorMessage(msg)
msg = antigravityProjectRefRegex.ReplaceAllString(msg, "projects/***")
msg = antigravityEmailRegex.ReplaceAllString(msg, "***")
msg = antigravityConsumerRegex.ReplaceAllString(msg, "$1$2***")
return msg
}
// buildAntigravityClientErrorBody 为客户端构造 Gemini 风格的错误体:
// 仅保留 code/status/message(message 已脱敏),丢弃 details 等可能含账号身份的字段。
func buildAntigravityClientErrorBody(statusCode int, body []byte) []byte {
code := statusCode
status := ""
message := ""
var parsed struct {
Error struct {
Code int `json:"code"`
Message string `json:"message"`
Status string `json:"status"`
} `json:"error"`
}
if err := json.Unmarshal(body, &parsed); err == nil {
if parsed.Error.Code != 0 {
code = parsed.Error.Code
}
status = parsed.Error.Status
message = parsed.Error.Message
}
if strings.TrimSpace(message) == "" {
message = strings.TrimSpace(extractUpstreamErrorMessage(body))
}
if strings.TrimSpace(message) == "" {
message = http.StatusText(statusCode)
if message == "" {
message = "Upstream request failed"
}
}
if status == "" {
status = antigravityGeminiStatusFromHTTP(statusCode)
}
out, err := json.Marshal(map[string]any{
"error": map[string]any{
"code": code,
"message": sanitizeAntigravityErrorText(message),
"status": status,
},
})
if err != nil {
return []byte(`{"error":{"code":500,"message":"Upstream request failed","status":"INTERNAL"}}`)
}
return out
}
func antigravityGeminiStatusFromHTTP(statusCode int) string {
switch statusCode {
case http.StatusBadRequest:
return "INVALID_ARGUMENT"
case http.StatusUnauthorized:
return "UNAUTHENTICATED"
case http.StatusForbidden:
return "PERMISSION_DENIED"
case http.StatusNotFound:
return "NOT_FOUND"
case http.StatusTooManyRequests:
return "RESOURCE_EXHAUSTED"
case http.StatusServiceUnavailable:
return "UNAVAILABLE"
case http.StatusGatewayTimeout:
return "DEADLINE_EXCEEDED"
default:
if statusCode >= 500 {
return "INTERNAL"
}
return "UNKNOWN"
}
}