package shhh import ( "crypto/sha256" "encoding/base64" "regexp" "sort" "strings" ) type compiledRule struct { name string regex *regexp.Regexp replacement string severity Severity tags []string } type matchRecord struct { value string } func (r *compiledRule) apply(in string) (string, []matchRecord) { indices := r.regex.FindAllStringSubmatchIndex(in, -1) if len(indices) == 0 { return in, nil } var builder strings.Builder builder.Grow(len(in)) matches := make([]matchRecord, 0, len(indices)) last := 0 for _, loc := range indices { start, end := loc[0], loc[1] builder.WriteString(in[last:start]) replaced := r.regex.ExpandString(nil, r.replacement, in, loc) builder.Write(replaced) matches = append(matches, matchRecord{value: in[start:end]}) last = end } builder.WriteString(in[last:]) return builder.String(), matches } func buildDefaultRuleConfigs(placeholder string) []RuleConfig { if placeholder == "" { placeholder = "[REDACTED]" } return []RuleConfig{ { Name: "bearer-token", Pattern: `(?i)(authorization\s*:\s*bearer\s+)([A-Za-z0-9\-._~+/]+=*)`, ReplacementTemplate: "$1" + placeholder, Severity: SeverityMedium, Tags: []string{"token", "http"}, }, { Name: "api-key", Pattern: `(?i)((?:api[_-]?key|token|secret|password)\s*[:=]\s*["']?)([A-Za-z0-9\-._~+/]{8,})(["']?)`, ReplacementTemplate: "$1" + placeholder + "$3", Severity: SeverityHigh, Tags: []string{"credentials"}, }, { Name: "openai-secret", Pattern: `(sk-[A-Za-z0-9]{20,})`, ReplacementTemplate: placeholder, Severity: SeverityHigh, Tags: []string{"llm", "api"}, }, { Name: "oauth-refresh-token", Pattern: `(?i)(refresh_token"?\s*[:=]\s*["']?)([A-Za-z0-9\-._~+/]{8,})(["']?)`, ReplacementTemplate: "$1" + placeholder + "$3", Severity: SeverityMedium, Tags: []string{"oauth"}, }, { Name: "private-key-block", Pattern: `(?s)(-----BEGIN [^-]+ PRIVATE KEY-----)[^-]+(-----END [^-]+ PRIVATE KEY-----)`, ReplacementTemplate: "$1\n" + placeholder + "\n$2", Severity: SeverityHigh, Tags: []string{"pem", "key"}, }, } } func compileRules(cfg Config, placeholder string) ([]*compiledRule, error) { configs := make([]RuleConfig, 0) if !cfg.DisableDefaultRules { configs = append(configs, buildDefaultRuleConfigs(placeholder)...) } configs = append(configs, cfg.CustomRules...) rules := make([]*compiledRule, 0, len(configs)) for _, rc := range configs { if rc.Name == "" || rc.Pattern == "" { continue } replacement := rc.ReplacementTemplate if replacement == "" { replacement = placeholder } re, err := regexp.Compile(rc.Pattern) if err != nil { return nil, err } compiled := &compiledRule{ name: rc.Name, replacement: replacement, regex: re, severity: rc.Severity, tags: append([]string(nil), rc.Tags...), } rules = append(rules, compiled) } sort.SliceStable(rules, func(i, j int) bool { return rules[i].name < rules[j].name }) return rules, nil } func hashSecret(value string) string { sum := sha256.Sum256([]byte(value)) return base64.RawStdEncoding.EncodeToString(sum[:]) }