From fc495e087d8d68a4b76f5ea9df22c1b1a159b602 Mon Sep 17 00:00:00 2001 From: spongehah <2635879218@qq.com> Date: Mon, 27 Jul 2026 17:40:11 +0800 Subject: [PATCH] feat(security-audit): allow Qwen3Guard auxiliary fields --- .../securityaudit/prompt_qwen3guard.go | 18 ++++++------------ .../securityaudit/prompt_qwen3guard_test.go | 14 +++++++++++++- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/backend/internal/securityaudit/prompt_qwen3guard.go b/backend/internal/securityaudit/prompt_qwen3guard.go index aa3e9ea9d..90d81cff3 100644 --- a/backend/internal/securityaudit/prompt_qwen3guard.go +++ b/backend/internal/securityaudit/prompt_qwen3guard.go @@ -86,19 +86,13 @@ func NormalizeCategory(value string) string { } func ParseQwen3Guard(content string, enabledScanners []string) (*NormalizedResult, error) { - lines := make([]string, 0, 2) - for _, line := range strings.Split(strings.ReplaceAll(content, "\r\n", "\n"), "\n") { - line = strings.TrimSpace(line) - if line != "" { - lines = append(lines, line) - } - } - if len(lines) != 2 { - return nil, &GuardError{Code: ErrorCodeInvalidResponse, Retryable: false} - } var safety string var categoryLine string - for _, line := range lines { + for _, line := range strings.Split(strings.ReplaceAll(content, "\r\n", "\n"), "\n") { + line = strings.TrimSpace(line) + if line == "" { + continue + } lower := strings.ToLower(line) switch { case strings.HasPrefix(lower, "safety:"): @@ -112,7 +106,7 @@ func ParseQwen3Guard(content string, enabledScanners []string) (*NormalizedResul } categoryLine = strings.TrimSpace(line[len("categories:"):]) default: - return nil, &GuardError{Code: ErrorCodeInvalidResponse} + // Auxiliary Guard fields, such as Refusal, do not affect audit decisions. } } switch strings.ToLower(safety) { diff --git a/backend/internal/securityaudit/prompt_qwen3guard_test.go b/backend/internal/securityaudit/prompt_qwen3guard_test.go index 5db035401..c5ab9944e 100644 --- a/backend/internal/securityaudit/prompt_qwen3guard_test.go +++ b/backend/internal/securityaudit/prompt_qwen3guard_test.go @@ -22,7 +22,7 @@ func TestParseQwen3GuardStrictAndPolicy(t *testing.T) { {"unsafe", "Safety: Unsafe\nCategories: Jailbreak", AllScannerIDs, EventCritical, ActionBlock, false}, {"unknown unsafe", "Safety: Unsafe\nCategories: Future Risk", AllScannerIDs, EventCritical, ActionBlock, false}, {"disabled unsafe warns", "Safety: Unsafe\nCategories: Violent", []string{"PII"}, EventFlag, ActionWarn, false}, - {"extra explanation", "Safety: Safe\nCategories: None\nThis is safe", AllScannerIDs, "", "", true}, + {"extra explanation", "Safety: Safe\nCategories: None\nThis is safe", AllScannerIDs, EventPass, ActionAllow, false}, {"duplicate", "Safety: Safe\nSafety: Safe", AllScannerIDs, "", "", true}, {"duplicate categories", "Safety: Safe\nCategories: None\nCategories: PII", AllScannerIDs, "", "", true}, {"missing categories", "Safety: Safe\n", AllScannerIDs, "", "", true}, @@ -42,6 +42,18 @@ func TestParseQwen3GuardStrictAndPolicy(t *testing.T) { } } +func TestParseQwen3GuardIgnoresAuxiliaryResponseFields(t *testing.T) { + result, err := ParseQwen3Guard("Safety: Unsafe\nCategories: Jailbreak\nRefusal: No", AllScannerIDs) + require.NoError(t, err) + require.Equal(t, "Unsafe", result.Safety) + require.Equal(t, []string{"jailbreak"}, result.Categories) + + serialized, err := json.Marshal(result) + require.NoError(t, err) + require.NotContains(t, string(serialized), "Refusal") + require.NotContains(t, string(serialized), "No") +} + func TestQwen3GuardOfficialCategoriesAliasesAndUnknownAreStable(t *testing.T) { official := "Violent, Non-violent Illegal Acts, Sexual Content or Sexual Acts, PII, Suicide & Self-Harm, Unethical Acts, Politically Sensitive Topics, Copyright Violation, Jailbreak" result, err := ParseQwen3Guard("Safety: Unsafe\nCategories: "+official, AllScannerIDs)