Sub2API v1.0 - AI API 网关(二开初始版本,基于上游 Wei-Shaw/sub2api)
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
package securityaudit
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParseQwen3GuardStrictAndPolicy(t *testing.T) {
|
||||
tests := []struct {
|
||||
name, output string
|
||||
enabled []string
|
||||
decision EventDecision
|
||||
action Action
|
||||
wantErr bool
|
||||
}{
|
||||
{"safe", "Safety: Safe\nCategories: None", AllScannerIDs, EventPass, ActionAllow, false},
|
||||
{"controversial", "Safety: Controversial\nCategories: Violent", AllScannerIDs, EventFlag, ActionWarn, false},
|
||||
{"controversial pii escalates", "Safety: Controversial\nCategories: PII", AllScannerIDs, EventCritical, ActionBlock, false},
|
||||
{"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, 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},
|
||||
{"unknown safety", "Safety: Maybe\nCategories: PII", AllScannerIDs, "", "", true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := ParseQwen3Guard(tt.output, tt.enabled)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.decision, result.Decision)
|
||||
require.Equal(t, tt.action, result.Action)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, AllScannerIDs, result.MatchedScanners)
|
||||
require.Empty(t, result.UnknownCategories)
|
||||
require.Equal(t, "priority", result.PolicyID)
|
||||
require.Equal(t, 1, result.PolicyVersion)
|
||||
|
||||
aliases := map[string]string{
|
||||
"violence": "violent", "non_violent_illegal_acts": "non_violent_illegal_acts",
|
||||
"sexual": "sexual_content_or_sexual_acts", "personal identifiable information": "pii",
|
||||
"suicide/self harm": "suicide_and_self_harm", "unethical": "unethical_acts",
|
||||
"political": "politically_sensitive_topics", "copyright": "copyright_violation",
|
||||
"prompt injection": "jailbreak",
|
||||
}
|
||||
for alias, canonical := range aliases {
|
||||
require.Equal(t, canonical, NormalizeCategory(alias), alias)
|
||||
}
|
||||
|
||||
const canary = "PROMPT_CANARY_RAW_UNKNOWN_CATEGORY"
|
||||
unknown, err := ParseQwen3Guard("Safety: Unsafe\nCategories: "+canary, AllScannerIDs)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, unknown.UnknownCategories, 1)
|
||||
require.NotContains(t, unknown.UnknownCategories[0], "canary")
|
||||
require.NotContains(t, unknown.UnknownCategories[0], "raw")
|
||||
require.Contains(t, unknown.UnknownCategories[0], "unknown:")
|
||||
}
|
||||
|
||||
func TestExtractOpenAIContentSupportsStringAndTextBlocks(t *testing.T) {
|
||||
content, err := extractOpenAIContent([]byte(`{"choices":[{"message":{"content":"Safety: Safe\nCategories: None"}}]}`))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "Safety: Safe\nCategories: None", content)
|
||||
content, err = extractOpenAIContent([]byte(`{"choices":[{"message":{"content":[{"type":"text","text":"Safety: Safe"},{"type":"text","text":"Categories: None"}]}}]}`))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "Safety: Safe\nCategories: None", content)
|
||||
for _, body := range []string{`{}`, `{"choices":[]}`, `{"choices":[{"message":{"content":null}}]}`} {
|
||||
_, err := extractOpenAIContent([]byte(body))
|
||||
require.Error(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregateRequiresEveryResult(t *testing.T) {
|
||||
_, err := AggregateResults([]*NormalizedResult{{Decision: EventPass, Action: ActionAllow}, nil}, 0)
|
||||
require.Error(t, err)
|
||||
result, err := AggregateResults([]*NormalizedResult{
|
||||
{Decision: EventPass, RiskLevel: RiskLow, Action: ActionAllow, Categories: []string{"pii"}},
|
||||
{Decision: EventCritical, RiskLevel: RiskCritical, Action: ActionBlock, Categories: []string{"jailbreak"}},
|
||||
}, 0)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, EventCritical, result.Decision)
|
||||
require.Equal(t, ActionBlock, result.Action)
|
||||
require.Equal(t, []string{"pii", "jailbreak"}, result.Categories)
|
||||
}
|
||||
|
||||
func TestAggregateDeduplicatesFactsAndUsesMostSevereEndpointMetadata(t *testing.T) {
|
||||
result, err := AggregateResults([]*NormalizedResult{
|
||||
{Decision: EventPass, RiskLevel: RiskLow, Action: ActionAllow, Safety: "Safe", Categories: []string{"pii"}, MatchedScanners: []string{"pii"}, ScannerScores: map[string]float64{"pii": 0}, ScannerEvidence: map[string]string{"pii": "first"}, GuardEndpointID: "safe-node", ScannerVersion: "safe-version", PolicyID: "priority", PolicyVersion: 1},
|
||||
{Decision: EventCritical, RiskLevel: RiskCritical, Action: ActionBlock, Safety: "Unsafe", Categories: []string{"pii", "jailbreak"}, MatchedScanners: []string{"pii", "jailbreak"}, ScannerScores: map[string]float64{"pii": 1, "jailbreak": 1}, ScannerEvidence: map[string]string{"pii": "second", "jailbreak": "blocked"}, GuardEndpointID: "block-node", ScannerVersion: "block-version", PolicyID: "priority", PolicyVersion: 2},
|
||||
}, 7*time.Millisecond)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []string{"pii", "jailbreak"}, result.Categories)
|
||||
require.Equal(t, []string{"pii", "jailbreak"}, result.MatchedScanners)
|
||||
require.Equal(t, "first", result.ScannerEvidence["pii"], "evidence is deterministically first-seen")
|
||||
require.Equal(t, "block-node", result.GuardEndpointID)
|
||||
require.Equal(t, "block-version", result.ScannerVersion)
|
||||
require.Equal(t, 2, result.PolicyVersion)
|
||||
require.Equal(t, 7, result.LatencyMS)
|
||||
}
|
||||
|
||||
func TestIssueSummariesAreDeterministicRedactedDerivedDTOs(t *testing.T) {
|
||||
const canary = "PROMPT_CANARY_EVIDENCE_SECRET"
|
||||
result := NormalizedResult{
|
||||
Decision: EventCritical, RiskLevel: RiskCritical, Action: ActionBlock,
|
||||
Categories: []string{"jailbreak", "pii"}, MatchedScanners: []string{"pii"},
|
||||
ScannerScores: map[string]float64{"pii": 1}, ScannerEvidence: map[string]string{"pii": canary},
|
||||
UnknownCategories: []string{unknownCategoryID("future risk")},
|
||||
}
|
||||
summaries := BuildIssueSummaries(result)
|
||||
require.Len(t, summaries, 3, "known categories are not hidden merely because policy disabled one")
|
||||
raw, err := json.Marshal(summaries)
|
||||
require.NoError(t, err)
|
||||
require.NotContains(t, string(raw), canary)
|
||||
for _, summary := range summaries {
|
||||
require.NotEmpty(t, summary.Title)
|
||||
require.NotEmpty(t, summary.Description)
|
||||
require.NotEmpty(t, summary.Code)
|
||||
require.NotEmpty(t, summary.EvidenceHash)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user