| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122 |
- 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
- }
|