Files
李建琦 6d655c9903
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
Sub2API v1.0 - AI API 网关(二开初始版本,基于上游 Wei-Shaw/sub2api)
2026-08-21 18:30:13 +08:00

123 lines
3.5 KiB
Go

package securityaudit
import (
"errors"
"sort"
"strings"
"time"
)
func SplitRunes(value string, limit int) []string {
if limit <= 0 {
return nil
}
segments := strings.Split(value, promptAuditPrioritySeparator)
chunks := make([]string, 0, len(segments))
for _, segment := range segments {
runes := []rune(segment)
for start := 0; start < len(runes); start += limit {
end := start + limit
if end > len(runes) {
end = len(runes)
}
chunks = append(chunks, string(runes[start:end]))
}
}
return chunks
}
func AggregateResults(results []*NormalizedResult, latency time.Duration) (*NormalizedResult, error) {
if len(results) == 0 {
return nil, errors.New("prompt guard produced no complete result")
}
aggregated := &NormalizedResult{
Decision: EventPass, RiskLevel: RiskLow, Action: ActionAllow,
ScannerBackend: "qwen3guard-openai", Categories: []string{}, MatchedScanners: []string{},
ScannerScores: map[string]float64{}, ScannerEvidence: map[string]string{}, ChunkTotal: len(results),
LatencyMS: int(latency.Milliseconds()),
}
categories := map[string]struct{}{}
matched := map[string]struct{}{}
unknown := map[string]struct{}{}
for _, result := range results {
if result == nil {
return nil, errors.New("prompt guard partial result is not allowed")
}
if resultSeverity(result.Decision) > resultSeverity(aggregated.Decision) {
aggregated.Decision = result.Decision
aggregated.RiskLevel = result.RiskLevel
aggregated.Action = result.Action
aggregated.Safety = result.Safety
aggregated.GuardEndpointID = result.GuardEndpointID
aggregated.ScannerVersion = result.ScannerVersion
aggregated.PolicyID = result.PolicyID
aggregated.PolicyVersion = result.PolicyVersion
}
if aggregated.GuardEndpointID == "" {
aggregated.GuardEndpointID = result.GuardEndpointID
aggregated.ScannerVersion = result.ScannerVersion
aggregated.PolicyID = result.PolicyID
aggregated.PolicyVersion = result.PolicyVersion
}
for _, category := range result.Categories {
categories[category] = struct{}{}
}
for _, scanner := range result.MatchedScanners {
matched[scanner] = struct{}{}
}
for scanner, score := range result.ScannerScores {
if score > aggregated.ScannerScores[scanner] {
aggregated.ScannerScores[scanner] = score
}
}
for scanner, evidence := range result.ScannerEvidence {
if _, exists := aggregated.ScannerEvidence[scanner]; !exists {
aggregated.ScannerEvidence[scanner] = RedactPreview(evidence, 160)
}
}
for _, category := range result.UnknownCategories {
unknown[category] = struct{}{}
}
}
aggregated.Categories = orderedScannerKeys(categories)
aggregated.MatchedScanners = orderedScannerKeys(matched)
aggregated.UnknownCategories = sortedKeys(unknown)
return aggregated, nil
}
func resultSeverity(decision EventDecision) int {
switch decision {
case EventCritical:
return 3
case EventFlag:
return 2
default:
return 1
}
}
func sortedKeys(values map[string]struct{}) []string {
result := make([]string, 0, len(values))
for key := range values {
result = append(result, key)
}
sort.Strings(result)
return result
}
func orderedScannerKeys(values map[string]struct{}) []string {
result := make([]string, 0, len(values))
remaining := make(map[string]struct{}, len(values))
for key := range values {
remaining[key] = struct{}{}
}
for _, scannerID := range AllScannerIDs {
if _, ok := remaining[scannerID]; ok {
result = append(result, scannerID)
delete(remaining, scannerID)
}
}
result = append(result, sortedKeys(remaining)...)
return result
}