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,122 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user