131 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			131 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 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[:])
 | |
| }
 | 
