package validator import ( "fmt" "strings" "browser.local/platform/domain" ) const ( maxAIInvocationPromptSize = 8000 maxAIInvocationConfigSize = 64 * 1024 maxAIInvocationContextRefs = 12 ) func ValidateAIInvocationRequest(request domain.AIInvocationRequest) error { request = domain.CopyAIInvocationRequest(request) var violations []string violations = appendRequired(violations, "requestId", request.RequestID) violations = appendRequired(violations, "purpose", request.Purpose) violations = appendRequired(violations, "prompt", request.Prompt) if request.Purpose != "" && !validAIPurpose(request.Purpose) { violations = append(violations, "purpose is not allowed") } if len([]byte(request.Prompt)) > maxAIInvocationPromptSize { violations = append(violations, "prompt is too large") } if len([]byte(request.CurrentConfig)) > maxAIInvocationConfigSize { violations = append(violations, "currentConfig is too large") } if request.ProviderID != "" && !safeIdentifier(request.ProviderID) { violations = append(violations, "providerId is invalid") } if request.Model != "" && unsafeAIString(request.Model) { violations = append(violations, "model is unsafe") } if len(request.ContextRefs) > maxAIInvocationContextRefs { violations = append(violations, "contextRefs has too many keys") } for key, value := range request.ContextRefs { if strings.TrimSpace(key) == "" || key != strings.TrimSpace(key) { violations = append(violations, "contextRefs key is invalid") } if !validAIContextRef(value) { violations = append(violations, fmt.Sprintf("contextRefs[%s] is invalid", key)) } } for _, value := range []fieldString{ {field: "requestId", value: request.RequestID}, {field: "prompt", value: request.Prompt}, {field: "currentConfig", value: request.CurrentConfig}, {field: "providerId", value: request.ProviderID}, {field: "model", value: request.Model}, } { if unsafeAIString(value.value) { violations = append(violations, value.field+" contains unsafe content") } } return finish(violations) } func ValidateAIInvocationResponse(response domain.AIInvocationResponse) error { var violations []string violations = appendRequired(violations, "requestId", response.RequestID) violations = appendRequired(violations, "purpose", response.Purpose) violations = appendRequired(violations, "status", response.Status) if unsafeAIString(response.Recommendation) { violations = append(violations, "recommendation contains unsafe content") } if response.ConfigRecommendation != nil && unsafeAIString(response.ConfigRecommendation.SuggestedConfig) { violations = append(violations, "configRecommendation contains unsafe content") } if response.Error != nil && unsafeAIString(response.Error.Message) { violations = append(violations, "error message contains unsafe content") } return finish(violations) } func validAIContextRef(value string) bool { trimmed := strings.TrimSpace(value) if trimmed == "" || trimmed != value || unsafeAIString(value) { return false } return strings.HasPrefix(value, "server://") || strings.HasPrefix(value, "log://") || strings.HasPrefix(value, "artifact://") || strings.HasPrefix(value, "input://") } func safeIdentifier(value string) bool { trimmed := strings.TrimSpace(value) if trimmed == "" || trimmed != value || len([]rune(value)) > 160 { return false } for _, char := range value { if (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || (char >= '0' && char <= '9') || char == '_' || char == '-' || char == '.' { continue } return false } return true } func unsafeAIString(value string) bool { return containsUnsafeRuntimeSecret(value) || looksLikeRawHostPath(value) || strings.Contains(strings.ToLower(value), "provider base url secret") }