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,173 @@
|
||||
package securityaudit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
EventConfigUpdated = "prompt_audit.config_updated"
|
||||
EventConfigLoaded = "prompt_guard.config_loaded"
|
||||
EventConfigReloadDegraded = "prompt_guard.config_reload_degraded"
|
||||
EventConfigTokenInvalid = "prompt_guard.config_token_invalid"
|
||||
EventProbeStarted = "prompt_audit.endpoint_probe_started"
|
||||
EventProbeFinished = "prompt_audit.endpoint_probe_finished"
|
||||
EventProbeFailed = "prompt_audit.endpoint_probe_failed"
|
||||
EventJobEnqueued = "prompt_audit.job_enqueued"
|
||||
EventEnqueueSkipped = "prompt_audit.enqueue_skipped"
|
||||
EventEnqueueDropped = "prompt_audit.enqueue_dropped"
|
||||
EventAuditStarted = "prompt_audit.started"
|
||||
EventProcessingReclaimed = "prompt_audit.processing_reclaimed"
|
||||
EventProcessed = "prompt_audit.processed"
|
||||
EventProcessFailed = "prompt_audit.process_failed"
|
||||
EventFindingRecorded = "prompt_audit.finding_recorded"
|
||||
EventChunkStarted = "prompt_audit.scan_chunk_started"
|
||||
EventChunkCompleted = "prompt_audit.scan_chunk_completed"
|
||||
EventChunkFailed = "prompt_audit.scan_chunk_failed"
|
||||
EventChunksAggregated = "prompt_audit.scan_chunks_aggregated"
|
||||
EventEvaluationStarted = "prompt_guard.evaluation_started"
|
||||
EventGuardAllowed = "prompt_guard.allowed"
|
||||
EventGuardBlocked = "prompt_guard.blocked"
|
||||
EventGuardFailed = "prompt_guard.failed"
|
||||
EventResultRecordFailed = "prompt_guard.result_record_failed"
|
||||
EventEventDeleted = "prompt_audit.event_deleted"
|
||||
EventEventsDeleted = "prompt_audit.events_deleted"
|
||||
EventDeletePreviewed = "prompt_audit.events_delete_previewed"
|
||||
EventEventsFilterDeleted = "prompt_audit.events_filter_deleted"
|
||||
)
|
||||
|
||||
var knownLogEvents = map[string]struct{}{
|
||||
EventConfigUpdated: {}, EventConfigLoaded: {}, EventConfigReloadDegraded: {}, EventConfigTokenInvalid: {},
|
||||
EventProbeStarted: {}, EventProbeFinished: {}, EventProbeFailed: {},
|
||||
EventJobEnqueued: {}, EventEnqueueSkipped: {}, EventEnqueueDropped: {},
|
||||
EventAuditStarted: {}, EventProcessingReclaimed: {}, EventProcessed: {}, EventProcessFailed: {}, EventFindingRecorded: {},
|
||||
EventChunkStarted: {}, EventChunkCompleted: {}, EventChunkFailed: {}, EventChunksAggregated: {},
|
||||
EventEvaluationStarted: {}, EventGuardAllowed: {}, EventGuardBlocked: {}, EventGuardFailed: {}, EventResultRecordFailed: {},
|
||||
EventEventDeleted: {}, EventEventsDeleted: {}, EventDeletePreviewed: {}, EventEventsFilterDeleted: {},
|
||||
}
|
||||
|
||||
var allowedLogFields = map[string]struct{}{
|
||||
"request_id": {}, "user_id": {}, "api_key_id": {}, "group_id": {}, "provider": {},
|
||||
"protocol": {}, "endpoint": {}, "model": {}, "job_id": {}, "event_id": {},
|
||||
"config_version": {}, "guard_endpoint_id": {}, "decision": {}, "risk_level": {},
|
||||
"action": {}, "chunk_index": {}, "chunk_total": {}, "chunk_chars": {}, "input_chars": {},
|
||||
"input_limit": {}, "latency_ms": {}, "status": {}, "error_code": {}, "error_kind": {},
|
||||
"queue_length": {}, "queue_capacity": {}, "stage": {}, "upstream_dispatched": {},
|
||||
"billing_preconsumed": {}, "worker_id": {}, "reclaimed_total": {}, "attempts": {},
|
||||
"max_attempts": {}, "claim_version": {}, "http_status": {}, "retryable": {},
|
||||
}
|
||||
|
||||
func LogInfo(event string, fields map[string]any) {
|
||||
if _, ok := knownLogEvents[event]; !ok {
|
||||
return
|
||||
}
|
||||
slog.LogAttrs(context.Background(), slog.LevelInfo, event, safeAttrs(fields)...)
|
||||
}
|
||||
func LogWarn(event string, fields map[string]any) {
|
||||
if _, ok := knownLogEvents[event]; !ok {
|
||||
return
|
||||
}
|
||||
slog.LogAttrs(context.Background(), slog.LevelWarn, event, safeAttrs(fields)...)
|
||||
}
|
||||
func LogError(event string, fields map[string]any) {
|
||||
if _, ok := knownLogEvents[event]; !ok {
|
||||
return
|
||||
}
|
||||
slog.LogAttrs(context.Background(), slog.LevelError, event, safeAttrs(fields)...)
|
||||
}
|
||||
|
||||
func safeAttrs(fields map[string]any) []slog.Attr {
|
||||
attrs := make([]slog.Attr, 0, len(fields))
|
||||
for key, value := range fields {
|
||||
key = strings.TrimSpace(key)
|
||||
if _, allowed := allowedLogFields[key]; !allowed {
|
||||
continue
|
||||
}
|
||||
if text, ok := value.(string); ok {
|
||||
if key == "error_kind" || key == "error_code" {
|
||||
value = stableErrorCode(text)
|
||||
} else {
|
||||
value = TrimRunes(strings.TrimSpace(text), 256)
|
||||
}
|
||||
}
|
||||
attrs = append(attrs, slog.Any(key, value))
|
||||
}
|
||||
return attrs
|
||||
}
|
||||
|
||||
func mergeLogFields(base map[string]any, extra map[string]any) map[string]any {
|
||||
result := make(map[string]any, len(base)+len(extra))
|
||||
for key, value := range base {
|
||||
result[key] = value
|
||||
}
|
||||
for key, value := range extra {
|
||||
result[key] = value
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func requestLogFields(req Request) map[string]any {
|
||||
return map[string]any{
|
||||
"request_id": req.RequestID, "user_id": req.UserID, "api_key_id": req.APIKeyID,
|
||||
"group_id": pointerLogID(req.GroupID), "provider": req.Provider, "protocol": req.Protocol,
|
||||
"endpoint": req.Endpoint, "model": req.Model, "stage": req.Stage,
|
||||
}
|
||||
}
|
||||
|
||||
func snapshotLogFields(snapshot PromptSnapshot) map[string]any {
|
||||
return map[string]any{
|
||||
"request_id": snapshot.RequestID, "user_id": snapshot.UserID, "api_key_id": snapshot.APIKeyID,
|
||||
"group_id": pointerLogID(snapshot.GroupID), "provider": snapshot.Provider, "protocol": snapshot.Protocol,
|
||||
"endpoint": snapshot.Endpoint, "model": snapshot.Model, "stage": snapshot.Stage,
|
||||
}
|
||||
}
|
||||
|
||||
func jobLogFields(job *Job) map[string]any {
|
||||
if job == nil {
|
||||
return map[string]any{}
|
||||
}
|
||||
fields := snapshotLogFields(job.Snapshot)
|
||||
fields["job_id"] = job.ID
|
||||
fields["config_version"] = job.ConfigVersion
|
||||
fields["claim_version"] = job.ClaimVersion
|
||||
return fields
|
||||
}
|
||||
|
||||
func stableErrorCode(code string) string {
|
||||
code = strings.ToLower(strings.TrimSpace(code))
|
||||
if code == "" {
|
||||
return "unknown_error"
|
||||
}
|
||||
for _, char := range code {
|
||||
if (char >= 'a' && char <= 'z') || (char >= '0' && char <= '9') || char == '_' || char == '-' || char == '.' {
|
||||
continue
|
||||
}
|
||||
return "redacted_error"
|
||||
}
|
||||
return TrimRunes(code, 64)
|
||||
}
|
||||
|
||||
func stableErrorMessage(code string) string {
|
||||
switch stableErrorCode(code) {
|
||||
case ErrorCodeBlocked:
|
||||
return "Prompt Guard blocked the request"
|
||||
case ErrorCodeUnavailable, "payload_store_unavailable", "payload_missing":
|
||||
return "Prompt Audit dependency is unavailable"
|
||||
case ErrorCodeInvalidResponse:
|
||||
return "Prompt Guard returned an invalid response"
|
||||
case "queue_full", "queue_admission_busy":
|
||||
return "Prompt Audit queue is unavailable"
|
||||
case "worker_panic":
|
||||
return "Prompt Audit worker failed"
|
||||
case "config_load_failed", "config_ttl_reload_failed", "config_invalidation_reload_failed":
|
||||
return "Prompt Audit configuration could not be loaded"
|
||||
default:
|
||||
return "Prompt Audit operation failed"
|
||||
}
|
||||
}
|
||||
|
||||
func sanitizeStoredError(code string) (string, string) {
|
||||
stableCode := stableErrorCode(code)
|
||||
return stableCode, TrimRunes(stableErrorMessage(stableCode), 160)
|
||||
}
|
||||
Reference in New Issue
Block a user