mirror of
https://github.com/alibaba/higress.git
synced 2026-06-09 04:37:31 +08:00
feat(ai-security-guard): structured x_higress deny response, error-path metrics, and AI logging (#3894)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: rinfx <yucheng.lxr@alibaba-inc.com>
This commit is contained in:
@@ -54,11 +54,14 @@ func HandleOpenAIImageGenerationRequestBody(ctx wrapper.HttpContext, config cfg.
|
||||
contentIndex := 0
|
||||
imageIndex := 0
|
||||
sessionID, _ := utils.GenerateHexID(20)
|
||||
currentSubmissionIndex := 0
|
||||
currentImageSubmissionIndex := 0
|
||||
var singleCall func()
|
||||
var singleCallForImage func()
|
||||
callback := func(statusCode int, responseHeaders http.Header, responseBody []byte) {
|
||||
log.Info(string(responseBody))
|
||||
if statusCode != 200 || gjson.GetBytes(responseBody, "Code").Int() != 200 {
|
||||
cfg.MarkGuardrailRequestError(ctx, currentSubmissionIndex, responseBody, startTime)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
return
|
||||
}
|
||||
@@ -66,6 +69,7 @@ func HandleOpenAIImageGenerationRequestBody(ctx wrapper.HttpContext, config cfg.
|
||||
err := json.Unmarshal(responseBody, &response)
|
||||
if err != nil {
|
||||
log.Errorf("%+v", err)
|
||||
cfg.MarkGuardrailRequestError(ctx, currentSubmissionIndex, responseBody, startTime)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
return
|
||||
}
|
||||
@@ -74,10 +78,13 @@ func HandleOpenAIImageGenerationRequestBody(ctx wrapper.HttpContext, config cfg.
|
||||
endTime := time.Now().UnixMilli()
|
||||
ctx.SetUserAttribute("safecheck_request_rt", endTime-startTime)
|
||||
ctx.SetUserAttribute("safecheck_status", "request pass")
|
||||
ctx.WriteUserAttributeToLogWithKey(wrapper.AILogKey)
|
||||
}
|
||||
cfg.CompleteGuardrailSubmissionEvent(ctx, currentSubmissionIndex, responseBody, cfg.GuardrailResultPass)
|
||||
if contentIndex >= len(content) {
|
||||
if len(images) > 0 && config.CheckRequestImage {
|
||||
singleCallForImage()
|
||||
} else {
|
||||
cfg.WriteGuardrailLog(ctx)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
}
|
||||
} else {
|
||||
@@ -88,6 +95,7 @@ func HandleOpenAIImageGenerationRequestBody(ctx wrapper.HttpContext, config cfg.
|
||||
denyBody, err := cfg.BuildDenyResponseBody(response, config, consumer)
|
||||
if err != nil {
|
||||
log.Errorf("failed to build deny response body: %v", err)
|
||||
cfg.MarkGuardrailRequestError(ctx, currentSubmissionIndex, responseBody, startTime)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
return
|
||||
}
|
||||
@@ -97,13 +105,15 @@ func HandleOpenAIImageGenerationRequestBody(ctx wrapper.HttpContext, config cfg.
|
||||
endTime := time.Now().UnixMilli()
|
||||
ctx.SetUserAttribute("safecheck_request_rt", endTime-startTime)
|
||||
ctx.SetUserAttribute("safecheck_status", "reqeust deny")
|
||||
if response.Data.Advice != nil {
|
||||
if len(response.Data.Result) > 0 {
|
||||
ctx.SetUserAttribute("safecheck_riskLabel", response.Data.Result[0].Label)
|
||||
ctx.SetUserAttribute("safecheck_riskWords", response.Data.Result[0].RiskWords)
|
||||
}
|
||||
ctx.WriteUserAttributeToLogWithKey(wrapper.AILogKey)
|
||||
cfg.CompleteGuardrailSubmissionEvent(ctx, currentSubmissionIndex, responseBody, cfg.GuardrailResultDeny)
|
||||
cfg.WriteGuardrailLog(ctx)
|
||||
}
|
||||
singleCall = func() {
|
||||
currentSubmissionIndex = cfg.BeginGuardrailSubmissionEvent(ctx, cfg.GuardrailPhaseRequest, cfg.GuardrailModalityText)
|
||||
var nextContentIndex int
|
||||
if contentIndex+cfg.LengthLimit >= len(content) {
|
||||
nextContentIndex = len(content)
|
||||
@@ -117,6 +127,7 @@ func HandleOpenAIImageGenerationRequestBody(ctx wrapper.HttpContext, config cfg.
|
||||
err := config.Client.Post(path, headers, body, callback, config.Timeout)
|
||||
if err != nil {
|
||||
log.Errorf("failed call the safe check service: %v", err)
|
||||
cfg.MarkGuardrailRequestError(ctx, currentSubmissionIndex, nil, startTime)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
}
|
||||
}
|
||||
@@ -125,6 +136,7 @@ func HandleOpenAIImageGenerationRequestBody(ctx wrapper.HttpContext, config cfg.
|
||||
imageIndex += 1
|
||||
log.Info(string(responseBody))
|
||||
if statusCode != 200 || gjson.GetBytes(responseBody, "Code").Int() != 200 {
|
||||
cfg.MarkGuardrailRequestError(ctx, currentImageSubmissionIndex, responseBody, startTime)
|
||||
if imageIndex < len(images) {
|
||||
singleCallForImage()
|
||||
} else {
|
||||
@@ -136,6 +148,7 @@ func HandleOpenAIImageGenerationRequestBody(ctx wrapper.HttpContext, config cfg.
|
||||
err := json.Unmarshal(responseBody, &response)
|
||||
if err != nil {
|
||||
log.Errorf("%+v", err)
|
||||
cfg.MarkGuardrailRequestError(ctx, currentImageSubmissionIndex, responseBody, startTime)
|
||||
if imageIndex < len(images) {
|
||||
singleCallForImage()
|
||||
} else {
|
||||
@@ -148,7 +161,10 @@ func HandleOpenAIImageGenerationRequestBody(ctx wrapper.HttpContext, config cfg.
|
||||
if imageIndex >= len(images) {
|
||||
ctx.SetUserAttribute("safecheck_request_rt", endTime-startTime)
|
||||
ctx.SetUserAttribute("safecheck_status", "request pass")
|
||||
ctx.WriteUserAttributeToLogWithKey(wrapper.AILogKey)
|
||||
}
|
||||
cfg.CompleteGuardrailSubmissionEvent(ctx, currentImageSubmissionIndex, responseBody, cfg.GuardrailResultPass)
|
||||
if imageIndex >= len(images) {
|
||||
cfg.WriteGuardrailLog(ctx)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
} else {
|
||||
singleCallForImage()
|
||||
@@ -159,6 +175,7 @@ func HandleOpenAIImageGenerationRequestBody(ctx wrapper.HttpContext, config cfg.
|
||||
denyBody, err := cfg.BuildDenyResponseBody(response, config, consumer)
|
||||
if err != nil {
|
||||
log.Errorf("failed to build deny response body: %v", err)
|
||||
cfg.MarkGuardrailRequestError(ctx, currentImageSubmissionIndex, responseBody, startTime)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
return
|
||||
}
|
||||
@@ -167,13 +184,15 @@ func HandleOpenAIImageGenerationRequestBody(ctx wrapper.HttpContext, config cfg.
|
||||
config.IncrementCounter("ai_sec_request_deny", 1)
|
||||
ctx.SetUserAttribute("safecheck_request_rt", endTime-startTime)
|
||||
ctx.SetUserAttribute("safecheck_status", "reqeust deny")
|
||||
if response.Data.Advice != nil {
|
||||
if len(response.Data.Result) > 0 {
|
||||
ctx.SetUserAttribute("safecheck_riskLabel", response.Data.Result[0].Label)
|
||||
ctx.SetUserAttribute("safecheck_riskWords", response.Data.Result[0].RiskWords)
|
||||
}
|
||||
ctx.WriteUserAttributeToLogWithKey(wrapper.AILogKey)
|
||||
cfg.CompleteGuardrailSubmissionEvent(ctx, currentImageSubmissionIndex, responseBody, cfg.GuardrailResultDeny)
|
||||
cfg.WriteGuardrailLog(ctx)
|
||||
}
|
||||
singleCallForImage = func() {
|
||||
currentImageSubmissionIndex = cfg.BeginGuardrailSubmissionEvent(ctx, cfg.GuardrailPhaseRequest, cfg.GuardrailModalityImage)
|
||||
img := images[imageIndex]
|
||||
imgUrl := ""
|
||||
imgBase64 := ""
|
||||
@@ -186,6 +205,7 @@ func HandleOpenAIImageGenerationRequestBody(ctx wrapper.HttpContext, config cfg.
|
||||
err := config.Client.Post(path, headers, body, callbackForImage, config.Timeout)
|
||||
if err != nil {
|
||||
log.Errorf("failed call the safe check service: %v", err)
|
||||
cfg.MarkGuardrailRequestError(ctx, currentImageSubmissionIndex, nil, startTime)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
}
|
||||
}
|
||||
@@ -207,11 +227,13 @@ func HandleOpenAIImageGenerationResponseBody(ctx wrapper.HttpContext, config cfg
|
||||
return types.ActionContinue
|
||||
}
|
||||
imageIndex := 0
|
||||
currentSubmissionIndex := 0
|
||||
var singleCall func()
|
||||
callback := func(statusCode int, responseHeaders http.Header, responseBody []byte) {
|
||||
imageIndex += 1
|
||||
log.Info(string(responseBody))
|
||||
if statusCode != 200 || gjson.GetBytes(responseBody, "Code").Int() != 200 {
|
||||
cfg.MarkGuardrailResponseError(ctx, currentSubmissionIndex, responseBody, startTime)
|
||||
if imageIndex < len(imgResults) {
|
||||
singleCall()
|
||||
} else {
|
||||
@@ -223,6 +245,7 @@ func HandleOpenAIImageGenerationResponseBody(ctx wrapper.HttpContext, config cfg
|
||||
err := json.Unmarshal(responseBody, &response)
|
||||
if err != nil {
|
||||
log.Errorf("%+v", err)
|
||||
cfg.MarkGuardrailResponseError(ctx, currentSubmissionIndex, responseBody, startTime)
|
||||
if imageIndex < len(imgResults) {
|
||||
singleCall()
|
||||
} else {
|
||||
@@ -233,9 +256,16 @@ func HandleOpenAIImageGenerationResponseBody(ctx wrapper.HttpContext, config cfg
|
||||
endTime := time.Now().UnixMilli()
|
||||
if cfg.IsRiskLevelAcceptable(config.Action, response.Data, config, consumer) {
|
||||
if imageIndex >= len(imgResults) {
|
||||
ctx.SetUserAttribute("safecheck_response_rt", endTime-startTime)
|
||||
// Transitional double-write: this handler historically wrote safecheck_request_rt
|
||||
// for response-phase emissions; existing dashboards key off the old name.
|
||||
// Remove after 1–2 release cycles.
|
||||
ctx.SetUserAttribute("safecheck_request_rt", endTime-startTime)
|
||||
ctx.SetUserAttribute("safecheck_status", "request pass")
|
||||
ctx.WriteUserAttributeToLogWithKey(wrapper.AILogKey)
|
||||
ctx.SetUserAttribute("safecheck_status", "response pass")
|
||||
}
|
||||
cfg.CompleteGuardrailSubmissionEvent(ctx, currentSubmissionIndex, responseBody, cfg.GuardrailResultPass)
|
||||
if imageIndex >= len(imgResults) {
|
||||
cfg.WriteGuardrailLog(ctx)
|
||||
proxywasm.ResumeHttpResponse()
|
||||
} else {
|
||||
singleCall()
|
||||
@@ -245,16 +275,25 @@ func HandleOpenAIImageGenerationResponseBody(ctx wrapper.HttpContext, config cfg
|
||||
denyBody, err := cfg.BuildDenyResponseBody(response, config, consumer)
|
||||
if err != nil {
|
||||
log.Errorf("failed to build deny response body: %v", err)
|
||||
cfg.MarkGuardrailResponseError(ctx, currentSubmissionIndex, responseBody, startTime)
|
||||
proxywasm.ResumeHttpResponse()
|
||||
return
|
||||
}
|
||||
proxywasm.SendHttpResponse(403, [][2]string{{"content-type", "application/json"}}, denyBody, -1)
|
||||
config.IncrementCounter("ai_sec_response_deny", 1)
|
||||
// Transitional double-write: this handler historically incremented ai_sec_request_deny
|
||||
// and wrote safecheck_request_rt for response-phase denies; existing dashboards/alerts
|
||||
// key off the old names. The legacy safecheck_status="reqeust deny" (typo) is dropped
|
||||
// in this transition. Remove after 1–2 release cycles.
|
||||
config.IncrementCounter("ai_sec_request_deny", 1)
|
||||
ctx.SetUserAttribute("safecheck_response_rt", endTime-startTime)
|
||||
ctx.SetUserAttribute("safecheck_request_rt", endTime-startTime)
|
||||
ctx.SetUserAttribute("safecheck_status", "reqeust deny")
|
||||
ctx.WriteUserAttributeToLogWithKey(wrapper.AILogKey)
|
||||
ctx.SetUserAttribute("safecheck_status", "response deny")
|
||||
cfg.CompleteGuardrailSubmissionEvent(ctx, currentSubmissionIndex, responseBody, cfg.GuardrailResultDeny)
|
||||
cfg.WriteGuardrailLog(ctx)
|
||||
}
|
||||
singleCall = func() {
|
||||
currentSubmissionIndex = cfg.BeginGuardrailSubmissionEvent(ctx, cfg.GuardrailPhaseResponse, cfg.GuardrailModalityImage)
|
||||
img := imgResults[imageIndex]
|
||||
imgUrl := ""
|
||||
imgBase64 := ""
|
||||
@@ -267,6 +306,7 @@ func HandleOpenAIImageGenerationResponseBody(ctx wrapper.HttpContext, config cfg
|
||||
err := config.Client.Post(path, headers, body, callback, config.Timeout)
|
||||
if err != nil {
|
||||
log.Errorf("failed call the safe check service: %v", err)
|
||||
cfg.MarkGuardrailResponseError(ctx, currentSubmissionIndex, nil, startTime)
|
||||
proxywasm.ResumeHttpResponse()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,11 +212,14 @@ func HandleQwenImageGenerationRequestBody(ctx wrapper.HttpContext, config cfg.AI
|
||||
contentIndex := 0
|
||||
imageIndex := 0
|
||||
sessionID, _ := utils.GenerateHexID(20)
|
||||
currentSubmissionIndex := 0
|
||||
currentImageSubmissionIndex := 0
|
||||
var singleCall func()
|
||||
var singleCallForImage func()
|
||||
callback := func(statusCode int, responseHeaders http.Header, responseBody []byte) {
|
||||
log.Info(string(responseBody))
|
||||
if statusCode != 200 || gjson.GetBytes(responseBody, "Code").Int() != 200 {
|
||||
cfg.MarkGuardrailRequestError(ctx, currentSubmissionIndex, responseBody, startTime)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
return
|
||||
}
|
||||
@@ -224,6 +227,7 @@ func HandleQwenImageGenerationRequestBody(ctx wrapper.HttpContext, config cfg.AI
|
||||
err := json.Unmarshal(responseBody, &response)
|
||||
if err != nil {
|
||||
log.Errorf("%+v", err)
|
||||
cfg.MarkGuardrailRequestError(ctx, currentSubmissionIndex, responseBody, startTime)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
return
|
||||
}
|
||||
@@ -232,10 +236,13 @@ func HandleQwenImageGenerationRequestBody(ctx wrapper.HttpContext, config cfg.AI
|
||||
endTime := time.Now().UnixMilli()
|
||||
ctx.SetUserAttribute("safecheck_request_rt", endTime-startTime)
|
||||
ctx.SetUserAttribute("safecheck_status", "request pass")
|
||||
ctx.WriteUserAttributeToLogWithKey(wrapper.AILogKey)
|
||||
}
|
||||
cfg.CompleteGuardrailSubmissionEvent(ctx, currentSubmissionIndex, responseBody, cfg.GuardrailResultPass)
|
||||
if contentIndex >= len(content) {
|
||||
if len(images) > 0 && config.CheckRequestImage {
|
||||
singleCallForImage()
|
||||
} else {
|
||||
cfg.WriteGuardrailLog(ctx)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
}
|
||||
} else {
|
||||
@@ -246,6 +253,7 @@ func HandleQwenImageGenerationRequestBody(ctx wrapper.HttpContext, config cfg.AI
|
||||
denyBody, err := cfg.BuildDenyResponseBody(response, config, consumer)
|
||||
if err != nil {
|
||||
log.Errorf("failed to build deny response body: %v", err)
|
||||
cfg.MarkGuardrailRequestError(ctx, currentSubmissionIndex, responseBody, startTime)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
return
|
||||
}
|
||||
@@ -255,13 +263,15 @@ func HandleQwenImageGenerationRequestBody(ctx wrapper.HttpContext, config cfg.AI
|
||||
endTime := time.Now().UnixMilli()
|
||||
ctx.SetUserAttribute("safecheck_request_rt", endTime-startTime)
|
||||
ctx.SetUserAttribute("safecheck_status", "reqeust deny")
|
||||
if response.Data.Advice != nil {
|
||||
if len(response.Data.Result) > 0 {
|
||||
ctx.SetUserAttribute("safecheck_riskLabel", response.Data.Result[0].Label)
|
||||
ctx.SetUserAttribute("safecheck_riskWords", response.Data.Result[0].RiskWords)
|
||||
}
|
||||
ctx.WriteUserAttributeToLogWithKey(wrapper.AILogKey)
|
||||
cfg.CompleteGuardrailSubmissionEvent(ctx, currentSubmissionIndex, responseBody, cfg.GuardrailResultDeny)
|
||||
cfg.WriteGuardrailLog(ctx)
|
||||
}
|
||||
singleCall = func() {
|
||||
currentSubmissionIndex = cfg.BeginGuardrailSubmissionEvent(ctx, cfg.GuardrailPhaseRequest, cfg.GuardrailModalityText)
|
||||
var nextContentIndex int
|
||||
if contentIndex+cfg.LengthLimit >= len(content) {
|
||||
nextContentIndex = len(content)
|
||||
@@ -275,6 +285,7 @@ func HandleQwenImageGenerationRequestBody(ctx wrapper.HttpContext, config cfg.AI
|
||||
err := config.Client.Post(path, headers, body, callback, config.Timeout)
|
||||
if err != nil {
|
||||
log.Errorf("failed call the safe check service: %v", err)
|
||||
cfg.MarkGuardrailRequestError(ctx, currentSubmissionIndex, nil, startTime)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
}
|
||||
}
|
||||
@@ -283,6 +294,7 @@ func HandleQwenImageGenerationRequestBody(ctx wrapper.HttpContext, config cfg.AI
|
||||
imageIndex += 1
|
||||
log.Info(string(responseBody))
|
||||
if statusCode != 200 || gjson.GetBytes(responseBody, "Code").Int() != 200 {
|
||||
cfg.MarkGuardrailRequestError(ctx, currentImageSubmissionIndex, responseBody, startTime)
|
||||
if imageIndex < len(images) {
|
||||
singleCallForImage()
|
||||
} else {
|
||||
@@ -294,6 +306,7 @@ func HandleQwenImageGenerationRequestBody(ctx wrapper.HttpContext, config cfg.AI
|
||||
err := json.Unmarshal(responseBody, &response)
|
||||
if err != nil {
|
||||
log.Errorf("%+v", err)
|
||||
cfg.MarkGuardrailRequestError(ctx, currentImageSubmissionIndex, responseBody, startTime)
|
||||
if imageIndex < len(images) {
|
||||
singleCallForImage()
|
||||
} else {
|
||||
@@ -306,7 +319,10 @@ func HandleQwenImageGenerationRequestBody(ctx wrapper.HttpContext, config cfg.AI
|
||||
if imageIndex >= len(images) {
|
||||
ctx.SetUserAttribute("safecheck_request_rt", endTime-startTime)
|
||||
ctx.SetUserAttribute("safecheck_status", "request pass")
|
||||
ctx.WriteUserAttributeToLogWithKey(wrapper.AILogKey)
|
||||
}
|
||||
cfg.CompleteGuardrailSubmissionEvent(ctx, currentImageSubmissionIndex, responseBody, cfg.GuardrailResultPass)
|
||||
if imageIndex >= len(images) {
|
||||
cfg.WriteGuardrailLog(ctx)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
} else {
|
||||
singleCallForImage()
|
||||
@@ -317,6 +333,7 @@ func HandleQwenImageGenerationRequestBody(ctx wrapper.HttpContext, config cfg.AI
|
||||
denyBody, err := cfg.BuildDenyResponseBody(response, config, consumer)
|
||||
if err != nil {
|
||||
log.Errorf("failed to build deny response body: %v", err)
|
||||
cfg.MarkGuardrailRequestError(ctx, currentImageSubmissionIndex, responseBody, startTime)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
return
|
||||
}
|
||||
@@ -325,13 +342,15 @@ func HandleQwenImageGenerationRequestBody(ctx wrapper.HttpContext, config cfg.AI
|
||||
config.IncrementCounter("ai_sec_request_deny", 1)
|
||||
ctx.SetUserAttribute("safecheck_request_rt", endTime-startTime)
|
||||
ctx.SetUserAttribute("safecheck_status", "reqeust deny")
|
||||
if response.Data.Advice != nil {
|
||||
if len(response.Data.Result) > 0 {
|
||||
ctx.SetUserAttribute("safecheck_riskLabel", response.Data.Result[0].Label)
|
||||
ctx.SetUserAttribute("safecheck_riskWords", response.Data.Result[0].RiskWords)
|
||||
}
|
||||
ctx.WriteUserAttributeToLogWithKey(wrapper.AILogKey)
|
||||
cfg.CompleteGuardrailSubmissionEvent(ctx, currentImageSubmissionIndex, responseBody, cfg.GuardrailResultDeny)
|
||||
cfg.WriteGuardrailLog(ctx)
|
||||
}
|
||||
singleCallForImage = func() {
|
||||
currentImageSubmissionIndex = cfg.BeginGuardrailSubmissionEvent(ctx, cfg.GuardrailPhaseRequest, cfg.GuardrailModalityImage)
|
||||
img := images[imageIndex]
|
||||
imgUrl := ""
|
||||
imgBase64 := ""
|
||||
@@ -344,6 +363,7 @@ func HandleQwenImageGenerationRequestBody(ctx wrapper.HttpContext, config cfg.AI
|
||||
err := config.Client.Post(path, headers, body, callbackForImage, config.Timeout)
|
||||
if err != nil {
|
||||
log.Errorf("failed call the safe check service: %v", err)
|
||||
cfg.MarkGuardrailRequestError(ctx, currentImageSubmissionIndex, nil, startTime)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
}
|
||||
}
|
||||
@@ -365,11 +385,13 @@ func HandleQwenImageGenerationResponseBody(ctx wrapper.HttpContext, config cfg.A
|
||||
return types.ActionContinue
|
||||
}
|
||||
imageIndex := 0
|
||||
currentSubmissionIndex := 0
|
||||
var singleCall func()
|
||||
callback := func(statusCode int, responseHeaders http.Header, responseBody []byte) {
|
||||
imageIndex += 1
|
||||
log.Info(string(responseBody))
|
||||
if statusCode != 200 || gjson.GetBytes(responseBody, "Code").Int() != 200 {
|
||||
cfg.MarkGuardrailResponseError(ctx, currentSubmissionIndex, responseBody, startTime)
|
||||
if imageIndex < len(imgUrls) {
|
||||
singleCall()
|
||||
} else {
|
||||
@@ -381,6 +403,7 @@ func HandleQwenImageGenerationResponseBody(ctx wrapper.HttpContext, config cfg.A
|
||||
err := json.Unmarshal(responseBody, &response)
|
||||
if err != nil {
|
||||
log.Errorf("%+v", err)
|
||||
cfg.MarkGuardrailResponseError(ctx, currentSubmissionIndex, responseBody, startTime)
|
||||
if imageIndex < len(imgUrls) {
|
||||
singleCall()
|
||||
} else {
|
||||
@@ -391,9 +414,16 @@ func HandleQwenImageGenerationResponseBody(ctx wrapper.HttpContext, config cfg.A
|
||||
endTime := time.Now().UnixMilli()
|
||||
if cfg.IsRiskLevelAcceptable(config.Action, response.Data, config, consumer) {
|
||||
if imageIndex >= len(imgUrls) {
|
||||
ctx.SetUserAttribute("safecheck_response_rt", endTime-startTime)
|
||||
// Transitional double-write: this handler historically wrote safecheck_request_rt
|
||||
// for response-phase emissions; existing dashboards key off the old name.
|
||||
// Remove after 1–2 release cycles.
|
||||
ctx.SetUserAttribute("safecheck_request_rt", endTime-startTime)
|
||||
ctx.SetUserAttribute("safecheck_status", "request pass")
|
||||
ctx.WriteUserAttributeToLogWithKey(wrapper.AILogKey)
|
||||
ctx.SetUserAttribute("safecheck_status", "response pass")
|
||||
}
|
||||
cfg.CompleteGuardrailSubmissionEvent(ctx, currentSubmissionIndex, responseBody, cfg.GuardrailResultPass)
|
||||
if imageIndex >= len(imgUrls) {
|
||||
cfg.WriteGuardrailLog(ctx)
|
||||
proxywasm.ResumeHttpResponse()
|
||||
} else {
|
||||
singleCall()
|
||||
@@ -403,21 +433,31 @@ func HandleQwenImageGenerationResponseBody(ctx wrapper.HttpContext, config cfg.A
|
||||
denyBody, err := cfg.BuildDenyResponseBody(response, config, consumer)
|
||||
if err != nil {
|
||||
log.Errorf("failed to build deny response body: %v", err)
|
||||
cfg.MarkGuardrailResponseError(ctx, currentSubmissionIndex, responseBody, startTime)
|
||||
proxywasm.ResumeHttpResponse()
|
||||
return
|
||||
}
|
||||
proxywasm.SendHttpResponse(403, [][2]string{{"content-type", "application/json"}}, denyBody, -1)
|
||||
config.IncrementCounter("ai_sec_response_deny", 1)
|
||||
// Transitional double-write: this handler historically incremented ai_sec_request_deny
|
||||
// and wrote safecheck_request_rt for response-phase denies; existing dashboards/alerts
|
||||
// key off the old names. The legacy safecheck_status="reqeust deny" (typo) is dropped
|
||||
// in this transition. Remove after 1–2 release cycles.
|
||||
config.IncrementCounter("ai_sec_request_deny", 1)
|
||||
ctx.SetUserAttribute("safecheck_response_rt", endTime-startTime)
|
||||
ctx.SetUserAttribute("safecheck_request_rt", endTime-startTime)
|
||||
ctx.SetUserAttribute("safecheck_status", "reqeust deny")
|
||||
ctx.WriteUserAttributeToLogWithKey(wrapper.AILogKey)
|
||||
ctx.SetUserAttribute("safecheck_status", "response deny")
|
||||
cfg.CompleteGuardrailSubmissionEvent(ctx, currentSubmissionIndex, responseBody, cfg.GuardrailResultDeny)
|
||||
cfg.WriteGuardrailLog(ctx)
|
||||
}
|
||||
singleCall = func() {
|
||||
currentSubmissionIndex = cfg.BeginGuardrailSubmissionEvent(ctx, cfg.GuardrailPhaseResponse, cfg.GuardrailModalityImage)
|
||||
imgUrl := imgUrls[imageIndex]
|
||||
path, headers, body := common.GenerateRequestForImage(config, cfg.MultiModalGuardForBase64, checkImageService, imgUrl, "")
|
||||
err := config.Client.Post(path, headers, body, callback, config.Timeout)
|
||||
if err != nil {
|
||||
log.Errorf("failed call the safe check service: %v", err)
|
||||
cfg.MarkGuardrailResponseError(ctx, currentSubmissionIndex, nil, startTime)
|
||||
proxywasm.ResumeHttpResponse()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,10 +43,12 @@ func HandleMcpRequestBody(ctx wrapper.HttpContext, config cfg.AISecurityConfig,
|
||||
}
|
||||
contentIndex := 0
|
||||
sessionID, _ := utils.GenerateHexID(20)
|
||||
currentSubmissionIndex := 0
|
||||
var singleCall func()
|
||||
callback := func(statusCode int, responseHeaders http.Header, responseBody []byte) {
|
||||
log.Info(string(responseBody))
|
||||
if statusCode != 200 || gjson.GetBytes(responseBody, "Code").Int() != 200 {
|
||||
cfg.MarkGuardrailRequestError(ctx, currentSubmissionIndex, responseBody, startTime)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
return
|
||||
}
|
||||
@@ -54,6 +56,7 @@ func HandleMcpRequestBody(ctx wrapper.HttpContext, config cfg.AISecurityConfig,
|
||||
err := json.Unmarshal(responseBody, &response)
|
||||
if err != nil {
|
||||
log.Errorf("%+v", err)
|
||||
cfg.MarkGuardrailRequestError(ctx, currentSubmissionIndex, responseBody, startTime)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
return
|
||||
}
|
||||
@@ -62,7 +65,10 @@ func HandleMcpRequestBody(ctx wrapper.HttpContext, config cfg.AISecurityConfig,
|
||||
endTime := time.Now().UnixMilli()
|
||||
ctx.SetUserAttribute("safecheck_request_rt", endTime-startTime)
|
||||
ctx.SetUserAttribute("safecheck_status", "request pass")
|
||||
ctx.WriteUserAttributeToLogWithKey(wrapper.AILogKey)
|
||||
}
|
||||
cfg.CompleteGuardrailSubmissionEvent(ctx, currentSubmissionIndex, responseBody, cfg.GuardrailResultPass)
|
||||
if contentIndex >= len(content) {
|
||||
cfg.WriteGuardrailLog(ctx)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
} else {
|
||||
singleCall()
|
||||
@@ -74,22 +80,25 @@ func HandleMcpRequestBody(ctx wrapper.HttpContext, config cfg.AISecurityConfig,
|
||||
endTime := time.Now().UnixMilli()
|
||||
ctx.SetUserAttribute("safecheck_request_rt", endTime-startTime)
|
||||
ctx.SetUserAttribute("safecheck_status", "request deny")
|
||||
if response.Data.Advice != nil {
|
||||
if len(response.Data.Result) > 0 {
|
||||
ctx.SetUserAttribute("safecheck_riskLabel", response.Data.Result[0].Label)
|
||||
ctx.SetUserAttribute("safecheck_riskWords", response.Data.Result[0].RiskWords)
|
||||
}
|
||||
ctx.WriteUserAttributeToLogWithKey(wrapper.AILogKey)
|
||||
denyBody, err := cfg.BuildDenyResponseBody(response, config, consumer)
|
||||
if err != nil {
|
||||
log.Errorf("failed to build deny response body: %v", err)
|
||||
cfg.MarkGuardrailRequestError(ctx, currentSubmissionIndex, responseBody, startTime)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
return
|
||||
}
|
||||
cfg.CompleteGuardrailSubmissionEvent(ctx, currentSubmissionIndex, responseBody, cfg.GuardrailResultDeny)
|
||||
cfg.WriteGuardrailLog(ctx)
|
||||
marshalledDenyMessage := wrapper.MarshalStr(string(denyBody))
|
||||
denyResponse := fmt.Sprintf(DenyResponse, marshalledDenyMessage)
|
||||
proxywasm.SendHttpResponse(uint32(config.DenyCode), [][2]string{{"content-type", "application/json"}}, []byte(denyResponse), -1)
|
||||
}
|
||||
singleCall = func() {
|
||||
currentSubmissionIndex = cfg.BeginGuardrailSubmissionEvent(ctx, cfg.GuardrailPhaseRequest, cfg.GuardrailModalityMCP)
|
||||
var nextContentIndex int
|
||||
if contentIndex+cfg.LengthLimit >= len(content) {
|
||||
nextContentIndex = len(content)
|
||||
@@ -103,6 +112,7 @@ func HandleMcpRequestBody(ctx wrapper.HttpContext, config cfg.AISecurityConfig,
|
||||
err := config.Client.Post(path, headers, body, callback, config.Timeout)
|
||||
if err != nil {
|
||||
log.Errorf("failed call the safe check service: %v", err)
|
||||
cfg.MarkGuardrailRequestError(ctx, currentSubmissionIndex, nil, startTime)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
}
|
||||
}
|
||||
@@ -114,6 +124,7 @@ func HandleMcpRequestBody(ctx wrapper.HttpContext, config cfg.AISecurityConfig,
|
||||
func HandleMcpStreamingResponseBody(ctx wrapper.HttpContext, config cfg.AISecurityConfig, data []byte, endOfStream bool) []byte {
|
||||
consumer, _ := ctx.GetContext("consumer").(string)
|
||||
var frontBuffer []byte
|
||||
currentSubmissionIndex := 0
|
||||
var singleCall func()
|
||||
callback := func(statusCode int, responseHeaders http.Header, responseBody []byte) {
|
||||
defer func() {
|
||||
@@ -122,6 +133,9 @@ func HandleMcpStreamingResponseBody(ctx wrapper.HttpContext, config cfg.AISecuri
|
||||
}()
|
||||
log.Info(string(responseBody))
|
||||
if statusCode != 200 || gjson.GetBytes(responseBody, "Code").Int() != 200 {
|
||||
ctx.SetUserAttribute("safecheck_status", "response error")
|
||||
cfg.CompleteGuardrailSubmissionEvent(ctx, currentSubmissionIndex, responseBody, cfg.GuardrailResultError)
|
||||
cfg.WriteGuardrailLog(ctx)
|
||||
proxywasm.InjectEncodedDataToFilterChain(frontBuffer, false)
|
||||
return
|
||||
}
|
||||
@@ -129,6 +143,9 @@ func HandleMcpStreamingResponseBody(ctx wrapper.HttpContext, config cfg.AISecuri
|
||||
err := json.Unmarshal(responseBody, &response)
|
||||
if err != nil {
|
||||
log.Error("failed to unmarshal aliyun content security response at response phase")
|
||||
ctx.SetUserAttribute("safecheck_status", "response error")
|
||||
cfg.CompleteGuardrailSubmissionEvent(ctx, currentSubmissionIndex, responseBody, cfg.GuardrailResultError)
|
||||
cfg.WriteGuardrailLog(ctx)
|
||||
proxywasm.InjectEncodedDataToFilterChain(frontBuffer, false)
|
||||
return
|
||||
}
|
||||
@@ -136,13 +153,20 @@ func HandleMcpStreamingResponseBody(ctx wrapper.HttpContext, config cfg.AISecuri
|
||||
denyBody, err := cfg.BuildDenyResponseBody(response, config, consumer)
|
||||
if err != nil {
|
||||
log.Errorf("failed to build deny response body: %v", err)
|
||||
ctx.SetUserAttribute("safecheck_status", "response error")
|
||||
cfg.CompleteGuardrailSubmissionEvent(ctx, currentSubmissionIndex, responseBody, cfg.GuardrailResultError)
|
||||
cfg.WriteGuardrailLog(ctx)
|
||||
proxywasm.InjectEncodedDataToFilterChain(frontBuffer, false)
|
||||
return
|
||||
}
|
||||
cfg.CompleteGuardrailSubmissionEvent(ctx, currentSubmissionIndex, responseBody, cfg.GuardrailResultDeny)
|
||||
cfg.WriteGuardrailLog(ctx)
|
||||
marshalledDenyMessage := wrapper.MarshalStr(string(denyBody))
|
||||
denySSEResponse := fmt.Sprintf(DenySSEResponse, marshalledDenyMessage)
|
||||
proxywasm.InjectEncodedDataToFilterChain([]byte(denySSEResponse), true)
|
||||
} else {
|
||||
cfg.CompleteGuardrailSubmissionEvent(ctx, currentSubmissionIndex, responseBody, cfg.GuardrailResultPass)
|
||||
cfg.WriteGuardrailLog(ctx)
|
||||
proxywasm.InjectEncodedDataToFilterChain(frontBuffer, false)
|
||||
}
|
||||
}
|
||||
@@ -158,10 +182,14 @@ func HandleMcpStreamingResponseBody(ctx wrapper.HttpContext, config cfg.AISecuri
|
||||
ctx.SetContext("during_call", true)
|
||||
checkService := config.GetResponseCheckService(consumer)
|
||||
sessionID, _ := utils.GenerateHexID(20)
|
||||
currentSubmissionIndex = cfg.BeginGuardrailSubmissionEvent(ctx, cfg.GuardrailPhaseResponse, cfg.GuardrailModalityMCP)
|
||||
path, headers, body := common.GenerateRequestForText(config, config.Action, checkService, msg, sessionID)
|
||||
err := config.Client.Post(path, headers, body, callback, config.Timeout)
|
||||
if err != nil {
|
||||
log.Errorf("failed call the safe check service: %v", err)
|
||||
ctx.SetUserAttribute("safecheck_status", "response error")
|
||||
cfg.CompleteGuardrailSubmissionEventWithRequestID(ctx, currentSubmissionIndex, "", cfg.GuardrailResultError)
|
||||
cfg.WriteGuardrailLog(ctx)
|
||||
proxywasm.InjectEncodedDataToFilterChain(frontBuffer, false)
|
||||
ctx.SetContext("during_call", false)
|
||||
}
|
||||
@@ -194,10 +222,12 @@ func HandleMcpResponseBody(ctx wrapper.HttpContext, config cfg.AISecurityConfig,
|
||||
}
|
||||
contentIndex := 0
|
||||
sessionID, _ := utils.GenerateHexID(20)
|
||||
currentSubmissionIndex := 0
|
||||
var singleCall func()
|
||||
callback := func(statusCode int, responseHeaders http.Header, responseBody []byte) {
|
||||
log.Info(string(responseBody))
|
||||
if statusCode != 200 || gjson.GetBytes(responseBody, "Code").Int() != 200 {
|
||||
cfg.MarkGuardrailResponseError(ctx, currentSubmissionIndex, responseBody, startTime)
|
||||
proxywasm.ResumeHttpResponse()
|
||||
return
|
||||
}
|
||||
@@ -205,6 +235,7 @@ func HandleMcpResponseBody(ctx wrapper.HttpContext, config cfg.AISecurityConfig,
|
||||
err := json.Unmarshal(responseBody, &response)
|
||||
if err != nil {
|
||||
log.Error("failed to unmarshal aliyun content security response at response phase")
|
||||
cfg.MarkGuardrailResponseError(ctx, currentSubmissionIndex, responseBody, startTime)
|
||||
proxywasm.ResumeHttpResponse()
|
||||
return
|
||||
}
|
||||
@@ -213,7 +244,10 @@ func HandleMcpResponseBody(ctx wrapper.HttpContext, config cfg.AISecurityConfig,
|
||||
endTime := time.Now().UnixMilli()
|
||||
ctx.SetUserAttribute("safecheck_response_rt", endTime-startTime)
|
||||
ctx.SetUserAttribute("safecheck_status", "response pass")
|
||||
ctx.WriteUserAttributeToLogWithKey(wrapper.AILogKey)
|
||||
}
|
||||
cfg.CompleteGuardrailSubmissionEvent(ctx, currentSubmissionIndex, responseBody, cfg.GuardrailResultPass)
|
||||
if contentIndex >= len(content) {
|
||||
cfg.WriteGuardrailLog(ctx)
|
||||
proxywasm.ResumeHttpResponse()
|
||||
} else {
|
||||
singleCall()
|
||||
@@ -224,17 +258,19 @@ func HandleMcpResponseBody(ctx wrapper.HttpContext, config cfg.AISecurityConfig,
|
||||
endTime := time.Now().UnixMilli()
|
||||
ctx.SetUserAttribute("safecheck_response_rt", endTime-startTime)
|
||||
ctx.SetUserAttribute("safecheck_status", "response deny")
|
||||
if response.Data.Advice != nil {
|
||||
if len(response.Data.Result) > 0 {
|
||||
ctx.SetUserAttribute("safecheck_riskLabel", response.Data.Result[0].Label)
|
||||
ctx.SetUserAttribute("safecheck_riskWords", response.Data.Result[0].RiskWords)
|
||||
}
|
||||
ctx.WriteUserAttributeToLogWithKey(wrapper.AILogKey)
|
||||
denyBody, err := cfg.BuildDenyResponseBody(response, config, consumer)
|
||||
if err != nil {
|
||||
log.Errorf("failed to build deny response body: %v", err)
|
||||
cfg.MarkGuardrailResponseError(ctx, currentSubmissionIndex, responseBody, startTime)
|
||||
proxywasm.ResumeHttpResponse()
|
||||
return
|
||||
}
|
||||
cfg.CompleteGuardrailSubmissionEvent(ctx, currentSubmissionIndex, responseBody, cfg.GuardrailResultDeny)
|
||||
cfg.WriteGuardrailLog(ctx)
|
||||
marshalledDenyMessage := wrapper.MarshalStr(string(denyBody))
|
||||
denyResponseBody := fmt.Sprintf(DenyResponse, marshalledDenyMessage)
|
||||
proxywasm.RemoveHttpResponseHeader("content-length")
|
||||
@@ -253,10 +289,12 @@ func HandleMcpResponseBody(ctx wrapper.HttpContext, config cfg.AISecurityConfig,
|
||||
contentIndex = nextContentIndex
|
||||
log.Debugf("current content piece: %s", contentPiece)
|
||||
checkService := config.GetResponseCheckService(consumer)
|
||||
currentSubmissionIndex = cfg.BeginGuardrailSubmissionEvent(ctx, cfg.GuardrailPhaseResponse, cfg.GuardrailModalityMCP)
|
||||
path, headers, body := common.GenerateRequestForText(config, config.Action, checkService, contentPiece, sessionID)
|
||||
err := config.Client.Post(path, headers, body, callback, config.Timeout)
|
||||
if err != nil {
|
||||
log.Errorf("failed call the safe check service: %v", err)
|
||||
cfg.MarkGuardrailResponseError(ctx, currentSubmissionIndex, nil, startTime)
|
||||
proxywasm.ResumeHttpResponse()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package text
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -67,6 +66,8 @@ func HandleTextGenerationRequestBody(ctx wrapper.HttpContext, config cfg.AISecur
|
||||
hasMasked := false
|
||||
maskedContent := []byte(content)
|
||||
sessionID, _ := utils.GenerateHexID(20)
|
||||
currentSubmissionIndex := 0
|
||||
currentImageSubmissionIndex := 0
|
||||
var singleCall func()
|
||||
var singleCallForImage func()
|
||||
// prevContentIndex tracks the start of the current chunk for masking replacement
|
||||
@@ -74,6 +75,7 @@ func HandleTextGenerationRequestBody(ctx wrapper.HttpContext, config cfg.AISecur
|
||||
callback := func(statusCode int, responseHeaders http.Header, responseBody []byte) {
|
||||
log.Info(string(responseBody))
|
||||
if statusCode != 200 || gjson.GetBytes(responseBody, "Code").Int() != 200 {
|
||||
cfg.MarkGuardrailRequestError(ctx, currentSubmissionIndex, responseBody, startTime)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
return
|
||||
}
|
||||
@@ -81,6 +83,7 @@ func HandleTextGenerationRequestBody(ctx wrapper.HttpContext, config cfg.AISecur
|
||||
err := json.Unmarshal(responseBody, &response)
|
||||
if err != nil {
|
||||
log.Errorf("%+v", err)
|
||||
cfg.MarkGuardrailRequestError(ctx, currentSubmissionIndex, responseBody, startTime)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
return
|
||||
}
|
||||
@@ -97,26 +100,17 @@ func HandleTextGenerationRequestBody(ctx wrapper.HttpContext, config cfg.AISecur
|
||||
if replaceErr != nil {
|
||||
log.Errorf("failed to replace request body content, falling back to block: %v", replaceErr)
|
||||
// Fall back to block to prevent leaking sensitive data
|
||||
denyMessage := cfg.DefaultDenyMessage
|
||||
if config.DenyMessage != "" {
|
||||
denyMessage = config.DenyMessage
|
||||
}
|
||||
marshalledDenyMessage := wrapper.MarshalStr(denyMessage)
|
||||
if config.ProtocolOriginal {
|
||||
proxywasm.SendHttpResponse(uint32(config.DenyCode), [][2]string{{"content-type", "application/json"}}, []byte(marshalledDenyMessage), -1)
|
||||
} else if gjson.GetBytes(body, "stream").Bool() {
|
||||
randomID := utils.GenerateRandomChatID()
|
||||
jsonData := []byte(fmt.Sprintf(cfg.OpenAIStreamResponseFormat, randomID, marshalledDenyMessage, randomID))
|
||||
proxywasm.SendHttpResponse(uint32(config.DenyCode), [][2]string{{"content-type", "text/event-stream;charset=UTF-8"}}, jsonData, -1)
|
||||
} else {
|
||||
randomID := utils.GenerateRandomChatID()
|
||||
jsonData := []byte(fmt.Sprintf(cfg.OpenAIResponseFormat, randomID, marshalledDenyMessage))
|
||||
proxywasm.SendHttpResponse(uint32(config.DenyCode), [][2]string{{"content-type", "application/json"}}, jsonData, -1)
|
||||
if sendErr := cfg.SendFallbackDenyResponse(config, gjson.GetBytes(body, "stream").Bool()); sendErr != nil {
|
||||
log.Errorf("failed to build deny response body: %v", sendErr)
|
||||
cfg.MarkGuardrailRequestError(ctx, currentSubmissionIndex, responseBody, startTime)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
return
|
||||
}
|
||||
ctx.DontReadResponseBody()
|
||||
config.IncrementCounter("ai_sec_request_deny", 1)
|
||||
ctx.SetUserAttribute("safecheck_status", "reqeust deny")
|
||||
ctx.WriteUserAttributeToLogWithKey(wrapper.AILogKey)
|
||||
cfg.CompleteGuardrailSubmissionEvent(ctx, currentSubmissionIndex, responseBody, cfg.GuardrailResultDeny)
|
||||
cfg.WriteGuardrailLog(ctx)
|
||||
return
|
||||
}
|
||||
proxywasm.ReplaceHttpRequestBody(newBody)
|
||||
@@ -125,10 +119,13 @@ func HandleTextGenerationRequestBody(ctx wrapper.HttpContext, config cfg.AISecur
|
||||
} else {
|
||||
ctx.SetUserAttribute("safecheck_status", "request pass")
|
||||
}
|
||||
ctx.WriteUserAttributeToLogWithKey(wrapper.AILogKey)
|
||||
}
|
||||
cfg.CompleteGuardrailSubmissionEvent(ctx, currentSubmissionIndex, responseBody, cfg.GuardrailResultPass)
|
||||
if contentIndex >= len(maskedContent) {
|
||||
if len(images) > 0 && config.CheckRequestImage {
|
||||
singleCallForImage()
|
||||
} else {
|
||||
cfg.WriteGuardrailLog(ctx)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
}
|
||||
} else {
|
||||
@@ -140,6 +137,30 @@ func HandleTextGenerationRequestBody(ctx wrapper.HttpContext, config cfg.AISecur
|
||||
if desensitization == "" {
|
||||
proxywasm.LogInfof("safecheck_action_source=mask_fallback_to_block, reason=empty_desensitization")
|
||||
log.Warnf("desensitization content is empty, falling back to block logic")
|
||||
// Keep this fallback separate from RiskBlock: legacy reuses the
|
||||
// original deny body in content, while structured emits an empty
|
||||
// fallback guardrail object.
|
||||
isStream := gjson.GetBytes(body, "stream").Bool()
|
||||
var sendErr error
|
||||
if !config.ProtocolOriginal && config.OpenAIDenyResponseFormat != cfg.OpenAIDenyResponseFormatStructured {
|
||||
sendErr = cfg.SendDenyResponse(config, response, consumer, isStream)
|
||||
} else {
|
||||
sendErr = cfg.SendFallbackDenyResponse(config, isStream)
|
||||
}
|
||||
if sendErr != nil {
|
||||
log.Errorf("failed to build deny response body: %v", sendErr)
|
||||
cfg.MarkGuardrailRequestError(ctx, currentSubmissionIndex, responseBody, startTime)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
return
|
||||
}
|
||||
ctx.DontReadResponseBody()
|
||||
config.IncrementCounter("ai_sec_request_deny", 1)
|
||||
endTime := time.Now().UnixMilli()
|
||||
ctx.SetUserAttribute("safecheck_request_rt", endTime-startTime)
|
||||
ctx.SetUserAttribute("safecheck_status", "reqeust deny")
|
||||
cfg.CompleteGuardrailSubmissionEvent(ctx, currentSubmissionIndex, responseBody, cfg.GuardrailResultDeny)
|
||||
cfg.WriteGuardrailLog(ctx)
|
||||
return
|
||||
} else {
|
||||
// Replace only the current chunk portion in maskedContent
|
||||
chunkStart := prevContentIndex
|
||||
@@ -156,28 +177,19 @@ func HandleTextGenerationRequestBody(ctx wrapper.HttpContext, config cfg.AISecur
|
||||
if replaceErr != nil {
|
||||
log.Errorf("failed to replace request body content, falling back to block: %v", replaceErr)
|
||||
// Fall back to block to prevent leaking sensitive data
|
||||
denyMessage := cfg.DefaultDenyMessage
|
||||
if config.DenyMessage != "" {
|
||||
denyMessage = config.DenyMessage
|
||||
}
|
||||
marshalledDenyMessage := wrapper.MarshalStr(denyMessage)
|
||||
if config.ProtocolOriginal {
|
||||
proxywasm.SendHttpResponse(uint32(config.DenyCode), [][2]string{{"content-type", "application/json"}}, []byte(marshalledDenyMessage), -1)
|
||||
} else if gjson.GetBytes(body, "stream").Bool() {
|
||||
randomID := utils.GenerateRandomChatID()
|
||||
jsonData := []byte(fmt.Sprintf(cfg.OpenAIStreamResponseFormat, randomID, marshalledDenyMessage, randomID))
|
||||
proxywasm.SendHttpResponse(uint32(config.DenyCode), [][2]string{{"content-type", "text/event-stream;charset=UTF-8"}}, jsonData, -1)
|
||||
} else {
|
||||
randomID := utils.GenerateRandomChatID()
|
||||
jsonData := []byte(fmt.Sprintf(cfg.OpenAIResponseFormat, randomID, marshalledDenyMessage))
|
||||
proxywasm.SendHttpResponse(uint32(config.DenyCode), [][2]string{{"content-type", "application/json"}}, jsonData, -1)
|
||||
if sendErr := cfg.SendFallbackDenyResponse(config, gjson.GetBytes(body, "stream").Bool()); sendErr != nil {
|
||||
log.Errorf("failed to build deny response body: %v", sendErr)
|
||||
cfg.MarkGuardrailRequestError(ctx, currentSubmissionIndex, responseBody, startTime)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
return
|
||||
}
|
||||
ctx.DontReadResponseBody()
|
||||
config.IncrementCounter("ai_sec_request_deny", 1)
|
||||
endTime := time.Now().UnixMilli()
|
||||
ctx.SetUserAttribute("safecheck_request_rt", endTime-startTime)
|
||||
ctx.SetUserAttribute("safecheck_status", "reqeust deny")
|
||||
ctx.WriteUserAttributeToLogWithKey(wrapper.AILogKey)
|
||||
cfg.CompleteGuardrailSubmissionEvent(ctx, currentSubmissionIndex, responseBody, cfg.GuardrailResultDeny)
|
||||
cfg.WriteGuardrailLog(ctx)
|
||||
return
|
||||
}
|
||||
proxywasm.ReplaceHttpRequestBody(newBody)
|
||||
@@ -185,52 +197,41 @@ func HandleTextGenerationRequestBody(ctx wrapper.HttpContext, config cfg.AISecur
|
||||
endTime := time.Now().UnixMilli()
|
||||
ctx.SetUserAttribute("safecheck_request_rt", endTime-startTime)
|
||||
ctx.SetUserAttribute("safecheck_status", "request mask")
|
||||
ctx.WriteUserAttributeToLogWithKey(wrapper.AILogKey)
|
||||
cfg.CompleteGuardrailSubmissionEvent(ctx, currentSubmissionIndex, responseBody, cfg.GuardrailResultMask)
|
||||
if len(images) > 0 && config.CheckRequestImage {
|
||||
singleCallForImage()
|
||||
} else {
|
||||
cfg.WriteGuardrailLog(ctx)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
}
|
||||
} else {
|
||||
cfg.CompleteGuardrailSubmissionEvent(ctx, currentSubmissionIndex, responseBody, cfg.GuardrailResultMask)
|
||||
singleCall()
|
||||
}
|
||||
return
|
||||
}
|
||||
// Fall through to block logic when desensitization is empty
|
||||
fallthrough
|
||||
case cfg.RiskBlock:
|
||||
denyBody, err := cfg.BuildDenyResponseBody(response, config, consumer)
|
||||
if err != nil {
|
||||
if err := cfg.SendDenyResponse(config, response, consumer, gjson.GetBytes(body, "stream").Bool()); err != nil {
|
||||
log.Errorf("failed to build deny response body: %v", err)
|
||||
cfg.MarkGuardrailRequestError(ctx, currentSubmissionIndex, responseBody, startTime)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
return
|
||||
}
|
||||
if config.ProtocolOriginal {
|
||||
proxywasm.SendHttpResponse(uint32(config.DenyCode), [][2]string{{"content-type", "application/json"}}, denyBody, -1)
|
||||
} else if gjson.GetBytes(body, "stream").Bool() {
|
||||
randomID := utils.GenerateRandomChatID()
|
||||
marshalledDenyMessage := wrapper.MarshalStr(string(denyBody))
|
||||
jsonData := []byte(fmt.Sprintf(cfg.OpenAIStreamResponseFormat, randomID, marshalledDenyMessage, randomID))
|
||||
proxywasm.SendHttpResponse(uint32(config.DenyCode), [][2]string{{"content-type", "text/event-stream;charset=UTF-8"}}, jsonData, -1)
|
||||
} else {
|
||||
randomID := utils.GenerateRandomChatID()
|
||||
marshalledDenyMessage := wrapper.MarshalStr(string(denyBody))
|
||||
jsonData := []byte(fmt.Sprintf(cfg.OpenAIResponseFormat, randomID, marshalledDenyMessage))
|
||||
proxywasm.SendHttpResponse(uint32(config.DenyCode), [][2]string{{"content-type", "application/json"}}, jsonData, -1)
|
||||
}
|
||||
ctx.DontReadResponseBody()
|
||||
config.IncrementCounter("ai_sec_request_deny", 1)
|
||||
endTime := time.Now().UnixMilli()
|
||||
ctx.SetUserAttribute("safecheck_request_rt", endTime-startTime)
|
||||
ctx.SetUserAttribute("safecheck_status", "reqeust deny")
|
||||
if response.Data.Advice != nil {
|
||||
if len(response.Data.Result) > 0 {
|
||||
ctx.SetUserAttribute("safecheck_riskLabel", response.Data.Result[0].Label)
|
||||
ctx.SetUserAttribute("safecheck_riskWords", response.Data.Result[0].RiskWords)
|
||||
}
|
||||
ctx.WriteUserAttributeToLogWithKey(wrapper.AILogKey)
|
||||
cfg.CompleteGuardrailSubmissionEvent(ctx, currentSubmissionIndex, responseBody, cfg.GuardrailResultDeny)
|
||||
cfg.WriteGuardrailLog(ctx)
|
||||
}
|
||||
}
|
||||
singleCall = func() {
|
||||
currentSubmissionIndex = cfg.BeginGuardrailSubmissionEvent(ctx, cfg.GuardrailPhaseRequest, cfg.GuardrailModalityText)
|
||||
prevContentIndex = contentIndex
|
||||
var nextContentIndex int
|
||||
if contentIndex+cfg.LengthLimit >= len(maskedContent) {
|
||||
@@ -245,6 +246,7 @@ func HandleTextGenerationRequestBody(ctx wrapper.HttpContext, config cfg.AISecur
|
||||
err := config.Client.Post(path, headers, body, callback, config.Timeout)
|
||||
if err != nil {
|
||||
log.Errorf("failed call the safe check service: %v", err)
|
||||
cfg.MarkGuardrailRequestError(ctx, currentSubmissionIndex, nil, startTime)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
}
|
||||
}
|
||||
@@ -253,6 +255,7 @@ func HandleTextGenerationRequestBody(ctx wrapper.HttpContext, config cfg.AISecur
|
||||
imageIndex += 1
|
||||
log.Info(string(responseBody))
|
||||
if statusCode != 200 || gjson.GetBytes(responseBody, "Code").Int() != 200 {
|
||||
cfg.MarkGuardrailRequestError(ctx, currentImageSubmissionIndex, responseBody, startTime)
|
||||
if imageIndex < len(images) {
|
||||
singleCallForImage()
|
||||
} else {
|
||||
@@ -264,6 +267,7 @@ func HandleTextGenerationRequestBody(ctx wrapper.HttpContext, config cfg.AISecur
|
||||
err := json.Unmarshal(responseBody, &response)
|
||||
if err != nil {
|
||||
log.Errorf("%+v", err)
|
||||
cfg.MarkGuardrailRequestError(ctx, currentImageSubmissionIndex, responseBody, startTime)
|
||||
if imageIndex < len(images) {
|
||||
singleCallForImage()
|
||||
} else {
|
||||
@@ -276,7 +280,10 @@ func HandleTextGenerationRequestBody(ctx wrapper.HttpContext, config cfg.AISecur
|
||||
if imageIndex >= len(images) {
|
||||
ctx.SetUserAttribute("safecheck_request_rt", endTime-startTime)
|
||||
ctx.SetUserAttribute("safecheck_status", "request pass")
|
||||
ctx.WriteUserAttributeToLogWithKey(wrapper.AILogKey)
|
||||
}
|
||||
cfg.CompleteGuardrailSubmissionEvent(ctx, currentImageSubmissionIndex, responseBody, cfg.GuardrailResultPass)
|
||||
if imageIndex >= len(images) {
|
||||
cfg.WriteGuardrailLog(ctx)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
} else {
|
||||
singleCallForImage()
|
||||
@@ -284,36 +291,25 @@ func HandleTextGenerationRequestBody(ctx wrapper.HttpContext, config cfg.AISecur
|
||||
return
|
||||
}
|
||||
|
||||
denyBody, err := cfg.BuildDenyResponseBody(response, config, consumer)
|
||||
if err != nil {
|
||||
if err := cfg.SendDenyResponse(config, response, consumer, gjson.GetBytes(body, "stream").Bool()); err != nil {
|
||||
log.Errorf("failed to build deny response body: %v", err)
|
||||
cfg.MarkGuardrailRequestError(ctx, currentImageSubmissionIndex, responseBody, startTime)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
return
|
||||
}
|
||||
if config.ProtocolOriginal {
|
||||
proxywasm.SendHttpResponse(uint32(config.DenyCode), [][2]string{{"content-type", "application/json"}}, denyBody, -1)
|
||||
} else if gjson.GetBytes(body, "stream").Bool() {
|
||||
randomID := utils.GenerateRandomChatID()
|
||||
marshalledDenyMessage := wrapper.MarshalStr(string(denyBody))
|
||||
jsonData := []byte(fmt.Sprintf(cfg.OpenAIStreamResponseFormat, randomID, marshalledDenyMessage, randomID))
|
||||
proxywasm.SendHttpResponse(uint32(config.DenyCode), [][2]string{{"content-type", "text/event-stream;charset=UTF-8"}}, jsonData, -1)
|
||||
} else {
|
||||
randomID := utils.GenerateRandomChatID()
|
||||
marshalledDenyMessage := wrapper.MarshalStr(string(denyBody))
|
||||
jsonData := []byte(fmt.Sprintf(cfg.OpenAIResponseFormat, randomID, marshalledDenyMessage))
|
||||
proxywasm.SendHttpResponse(uint32(config.DenyCode), [][2]string{{"content-type", "application/json"}}, jsonData, -1)
|
||||
}
|
||||
ctx.DontReadResponseBody()
|
||||
config.IncrementCounter("ai_sec_request_deny", 1)
|
||||
ctx.SetUserAttribute("safecheck_request_rt", endTime-startTime)
|
||||
ctx.SetUserAttribute("safecheck_status", "reqeust deny")
|
||||
if response.Data.Advice != nil {
|
||||
if len(response.Data.Result) > 0 {
|
||||
ctx.SetUserAttribute("safecheck_riskLabel", response.Data.Result[0].Label)
|
||||
ctx.SetUserAttribute("safecheck_riskWords", response.Data.Result[0].RiskWords)
|
||||
}
|
||||
ctx.WriteUserAttributeToLogWithKey(wrapper.AILogKey)
|
||||
cfg.CompleteGuardrailSubmissionEvent(ctx, currentImageSubmissionIndex, responseBody, cfg.GuardrailResultDeny)
|
||||
cfg.WriteGuardrailLog(ctx)
|
||||
}
|
||||
singleCallForImage = func() {
|
||||
currentImageSubmissionIndex = cfg.BeginGuardrailSubmissionEvent(ctx, cfg.GuardrailPhaseRequest, cfg.GuardrailModalityImage)
|
||||
img := images[imageIndex]
|
||||
imgUrl := ""
|
||||
imgBase64 := ""
|
||||
@@ -326,6 +322,7 @@ func HandleTextGenerationRequestBody(ctx wrapper.HttpContext, config cfg.AISecur
|
||||
err := config.Client.Post(path, headers, body, callbackForImage, config.Timeout)
|
||||
if err != nil {
|
||||
log.Errorf("failed call the safe check service: %v", err)
|
||||
cfg.MarkGuardrailRequestError(ctx, currentImageSubmissionIndex, nil, startTime)
|
||||
proxywasm.ResumeHttpRequest()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user