Files
sub2api/backend/internal/handler/security_audit_errors.go
T
feeeei 3abd646582 fix(ops): Gemini 原生路由的网关本地错误按错误类型归类
Google 格式错误体没有 type 字段,ops 一律记为 api_error,客户端参数错误、
引用不存在的缓存等都被归为平台内部错误。

- googleError 写出时按状态码向 ops 登记错误类型:400/413 invalid_request_error、
  401 authentication_error、403 permission_error、404 not_found_error,
  429 与 5xx 不登记
- 计费、并发与风控拦截沿用 Claude / OpenAI 格式错误的类型与错误码,归类口径一致
- 存储价未配置、显式缓存未启用改用 googlePlatformError,仍按平台错误统计;
  错误透传规则改写的上游错误不登记本地类型
- 上游返回的缓存不存在标记为请求级失败:按客户端请求错误分类,
  上游原始错误保留为明细但不参与上游归因
2026-10-10 23:37:48 +08:00

193 lines
6.4 KiB
Go

package handler
import (
"context"
"encoding/json"
"net/http"
"strings"
"time"
"github.com/Wei-Shaw/sub2api/internal/pkg/googleapi"
"github.com/Wei-Shaw/sub2api/internal/securityaudit"
"github.com/Wei-Shaw/sub2api/internal/service"
coderws "github.com/coder/websocket"
"github.com/gin-gonic/gin"
)
func (h *OpenAIGatewayHandler) openAISecurityAuditError(c *gin.Context, decision *securityaudit.Decision) {
if decision == nil {
return
}
if decision.Legacy != nil && decision.Legacy.Blocked {
h.errorResponse(c, securityAuditStatus(decision), securityAuditErrorCode(decision), securityAuditMessage(decision))
return
}
errType := "api_error"
if decision.Kind == securityaudit.DecisionBlock {
errType = "permission_error"
}
c.JSON(securityAuditStatus(decision), gin.H{"error": gin.H{
"type": errType, "code": securityAuditErrorCode(decision), "message": securityAuditMessage(decision),
}})
}
func (h *GatewayHandler) openAISecurityAuditError(c *gin.Context, decision *securityaudit.Decision) {
if decision == nil {
return
}
if decision.Legacy != nil && decision.Legacy.Blocked {
h.chatCompletionsErrorResponse(c, securityAuditStatus(decision), securityAuditErrorCode(decision), securityAuditMessage(decision))
return
}
errType := "api_error"
if decision.Kind == securityaudit.DecisionBlock {
errType = "permission_error"
}
c.JSON(securityAuditStatus(decision), gin.H{"error": gin.H{
"type": errType, "code": securityAuditErrorCode(decision), "message": securityAuditMessage(decision),
}})
}
func (h *GatewayHandler) responsesSecurityAuditError(c *gin.Context, decision *securityaudit.Decision) {
if decision == nil {
return
}
if decision.Legacy != nil && decision.Legacy.Blocked {
h.responsesErrorResponse(c, securityAuditStatus(decision), securityAuditErrorCode(decision), securityAuditMessage(decision))
return
}
c.JSON(securityAuditStatus(decision), gin.H{"error": gin.H{
"type": "api_error", "code": securityAuditErrorCode(decision), "message": securityAuditMessage(decision),
}})
}
func (h *GatewayHandler) anthropicSecurityAuditError(c *gin.Context, decision *securityaudit.Decision) {
if decision == nil {
return
}
if decision.Legacy != nil && decision.Legacy.Blocked {
h.errorResponse(c, securityAuditStatus(decision), securityAuditErrorCode(decision), securityAuditMessage(decision))
return
}
errType := "api_error"
if decision.Kind == securityaudit.DecisionBlock {
errType = "permission_error"
}
c.JSON(securityAuditStatus(decision), gin.H{"type": "error", "error": gin.H{
"type": errType, "code": securityAuditErrorCode(decision), "message": securityAuditMessage(decision),
}})
}
func (h *OpenAIGatewayHandler) anthropicSecurityAuditError(c *gin.Context, decision *securityaudit.Decision) {
if decision == nil {
return
}
if decision.Legacy != nil && decision.Legacy.Blocked {
h.anthropicErrorResponse(c, securityAuditStatus(decision), securityAuditErrorCode(decision), securityAuditMessage(decision))
return
}
errType := "api_error"
if decision.Kind == securityaudit.DecisionBlock {
errType = "permission_error"
}
c.JSON(securityAuditStatus(decision), gin.H{"type": "error", "error": gin.H{
"type": errType, "code": securityAuditErrorCode(decision), "message": securityAuditMessage(decision),
}})
}
func googleSecurityAuditError(c *gin.Context, decision *securityaudit.Decision) {
if decision == nil {
return
}
if decision.Legacy != nil && decision.Legacy.Blocked {
googleErrorWithType(c, securityAuditStatus(decision), securityAuditErrorCode(decision), "", securityAuditMessage(decision))
return
}
errType := "api_error"
if decision.Kind == securityaudit.DecisionBlock {
errType = "permission_error"
}
service.SetOpsLocalErrorType(c, errType, securityAuditErrorCode(decision))
status := securityAuditStatus(decision)
googleStatus := googleapi.HTTPStatusToGoogleStatus(status)
if status == http.StatusServiceUnavailable {
googleStatus = "UNAVAILABLE"
}
requestID := ""
if c != nil && c.Request != nil {
requestID = contentModerationRequestID(c.Request.Context())
}
c.JSON(status, gin.H{"error": gin.H{
"code": status, "message": securityAuditMessage(decision), "status": googleStatus,
"details": []gin.H{{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": securityAuditErrorCode(decision), "domain": "sub2api.securityaudit",
"metadata": gin.H{"request_id": requestID},
}},
}})
}
func writeSecurityAuditWSError(ctx context.Context, conn *coderws.Conn, decision *securityaudit.Decision) {
if conn == nil || decision == nil {
return
}
if decision.Legacy != nil && decision.Legacy.Blocked {
legacy := decision.Legacy
writeContentModerationWSError(ctx, conn, (legacyContentModerationDecision{legacy}).toService())
return
}
if ctx == nil {
ctx = context.Background()
}
payload, err := json.Marshal(gin.H{
"event_id": "evt_prompt_guard_rejected", "type": "error",
"error": gin.H{"type": "invalid_request_error", "code": securityAuditErrorCode(decision), "message": securityAuditMessage(decision)},
})
if err != nil {
return
}
writeCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
_ = conn.Write(writeCtx, coderws.MessageText, payload)
}
type legacyContentModerationDecision struct{ value *securityaudit.LegacyDecision }
func (d legacyContentModerationDecision) toService() *service.ContentModerationDecision {
if d.value == nil {
return nil
}
return &service.ContentModerationDecision{Allowed: d.value.Allowed, Blocked: d.value.Blocked, Flagged: d.value.Flagged, Message: d.value.Message, StatusCode: d.value.StatusCode, Action: d.value.Action}
}
func securityAuditWSCloseStatus(decision *securityaudit.Decision) coderws.StatusCode {
if decision == nil {
return coderws.StatusInternalError
}
if decision.Legacy != nil && decision.Legacy.Blocked {
return coderws.StatusPolicyViolation
}
if decision.Kind == securityaudit.DecisionBlock {
return coderws.StatusCode(4403)
}
return coderws.StatusTryAgainLater
}
func securityAuditWSCloseReason(decision *securityaudit.Decision) string {
if decision == nil {
return securityaudit.ErrorCodeUnavailable
}
if decision.Legacy != nil && decision.Legacy.Blocked {
message := strings.TrimSpace(decision.Legacy.Message)
if message != "" {
return message
}
return "content_policy_violation"
}
code := securityAuditErrorCode(decision)
if code == "" {
return securityaudit.ErrorCodeUnavailable
}
return code
}