package engine import ( "regexp" "strings" "time" "github.com/salvacybersec/keyhunter/pkg/providers" "github.com/salvacybersec/keyhunter/pkg/types" ) // Detect applies provider regex patterns and optional entropy checks to a chunk. // It returns all findings from the chunk. func Detect(chunk types.Chunk, providerList []providers.Provider) []Finding { var findings []Finding content := string(chunk.Data) for _, p := range providerList { for _, pat := range p.Patterns { re, err := regexp.Compile(pat.Regex) if err != nil { continue // invalid regex -- skip silently } matches := re.FindAllString(content, -1) for _, match := range matches { // Apply entropy check if threshold is set if pat.EntropyMin > 0 && Shannon(match) < pat.EntropyMin { continue // too low entropy -- likely a placeholder } line := lineNumber(content, match) findings = append(findings, Finding{ ProviderName: p.Name, KeyValue: match, KeyMasked: MaskKey(match), Confidence: pat.Confidence, Source: chunk.Source, SourceType: "file", LineNumber: line, Offset: chunk.Offset, DetectedAt: time.Now(), }) } } } return findings } // lineNumber returns the 1-based line number where match first appears in content. func lineNumber(content, match string) int { idx := strings.Index(content, match) if idx < 0 { return 0 } return strings.Count(content[:idx], "\n") + 1 }