package security import ( "regexp" "strings" "unicode" ) const ( // MaxCampaignPromptRunes caps custom campaign / brand AI prompts (prompt-injection surface). MaxCampaignPromptRunes = 8000 // MaxBrandFieldRunes caps individual brand kit text fields. MaxBrandFieldRunes = 2000 // MaxBrandListItems caps dos/donts/preferred_terms entries. MaxBrandListItems = 40 // MaxBrandListItemRunes caps each list entry. MaxBrandListItemRunes = 200 ) var injectPhrase = regexp.MustCompile(`(?i)(ignore\s+previous|system\s*:|assistant\s*:|<\s*/?\s*script)`) // SanitizePrompt strips controls, soft-filters injection phrases, and truncates by runes. func SanitizePrompt(s string, maxRunes int) string { if maxRunes <= 0 { maxRunes = MaxCampaignPromptRunes } s = stripControls(strings.TrimSpace(s)) s = injectPhrase.ReplaceAllString(s, "[filtered]") return TruncateRunes(s, maxRunes) } // CapPromptLength reports whether s exceeds max (rune count). func CapPromptLength(s string, maxRunes int) bool { if maxRunes <= 0 { maxRunes = MaxCampaignPromptRunes } n := 0 for range s { n++ if n > maxRunes { return true } } return false } // SanitizeBrandList cleans and bounds a brand kit string list. func SanitizeBrandList(in []string) []string { if len(in) == 0 { return []string{} } out := make([]string, 0, len(in)) seen := map[string]struct{}{} for _, s := range in { s = SanitizePrompt(s, MaxBrandListItemRunes) if s == "" { continue } key := strings.ToLower(s) if _, ok := seen[key]; ok { continue } seen[key] = struct{}{} out = append(out, s) if len(out) >= MaxBrandListItems { break } } return out } func stripControls(s string) string { if s == "" { return "" } var b strings.Builder b.Grow(len(s)) for _, r := range s { if r == '\n' || r == '\t' || unicode.IsPrint(r) { b.WriteRune(r) } } return b.String() } // TruncateRunes truncates s to at most max runes. func TruncateRunes(s string, max int) string { if max <= 0 { return "" } n := 0 for i := range s { if n == max { return s[:i] } n++ } return s }