package bundle import "strings" // ScriptOtherAnalyzer covers the executable-text formats the threat model says // scanners ignore: .mjs/.cjs/.ts/.rb/.ps1/.bat/.lua/.pl and by-name // Makefile/Dockerfile/justfile (KindScriptOther). It reuses the shared // language-agnostic source/sink vocabulary plus a per-language family pass so // no executable text format stays sub-threshold. type ScriptOtherAnalyzer struct{} func (ScriptOtherAnalyzer) Name() string { return "script-other" } func (ScriptOtherAnalyzer) Handles(kind FileKind) bool { return kind == KindScriptOther } // scriptOtherIndicatorFamilies maps a language/format to the network-sink and // exec primitives that, when paired with an env/secret read, indicate exfil. // These augment the shared vocabulary with idioms specific to each runtime. var scriptOtherIndicatorFamilies = map[string][]string{ "node": {"child_process", "require('child_process')", "exec(", "execsync(", "spawn(", "https.request", "http.request", "net.connect"}, "ruby": {"net::http", "open-uri", "open(\"|", "system(", "`", "%x{", "exec(", "io.popen"}, "powershell": {"invoke-webrequest", "invoke-restmethod", "iwr ", "irm ", "start-process", "downloadstring", "system.net.webclient", "invoke-expression", "iex "}, "batch": {"powershell", "bitsadmin", "certutil -urlcache", "start /b", "reg add"}, "lua": {"os.execute", "io.popen", "socket.http", "require(\"socket"}, "perl": {"lwp::useragent", "system(", "exec(", "`", "io::socket", "use net::"}, "make": {"curl ", "wget ", "$(shell", "&& sh", "| sh", "| bash"}, "docker": {"run curl", "run wget", "run pip", "run npm", "add http", "&& sh", "| sh"}, } func (ScriptOtherAnalyzer) Analyze(f *File, b *Bundle) ([]Finding, error) { if f == nil { return nil, nil } text := string(f.Sniff) out := sharedIndicatorScan(text, f.RelPath, "script-other") // Per-language: a runtime exec/network primitive co-occurring with an // env/secret source is SevHigh+Corroborated even when the shared vocab // missed the exact sink token. lang := strings.ToLower(f.ScriptLang) fam := scriptOtherIndicatorFamilies[lang] if len(fam) > 0 { lines := strings.Split(text, "\n") lower := make([]string, len(lines)) for i, l := range lines { lower[i] = strings.ToLower(l) } for i := range lower { window := lower[i] if i+1 < len(lower) { window += "\n" + lower[i+1] } if i+2 < len(lower) { window += "\n" + lower[i+2] } hasPrimitive := matchedAny(window, fam) hasSource := matchedAny(window, envSourceTerms) if hasPrimitive && hasSource { out = append(out, Finding{ Analyzer: "script-other", File: f.RelPath, Signal: "exfil-env-to-network", Severity: SevHigh, Detail: lang + " runtime exec/network primitive co-occurs with env/credential read", Line: i + 1, Corroborated: true, }) } } } if pe, ok := paddingEvasionFinding(f, "script-other"); ok { out = append(out, pe) } return dedupeFindings(out), nil }