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
123 lines
3.5 KiB
Go
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
|
|
}
|