| package bundle | |
| import ( | |
| "path/filepath" | |
| "strings" | |
| ) | |
| // allowlist.go holds the known-benign-pattern allowlist that controls the | |
| // false-positive blast radius of bundle scanning. Many legitimate skills ship a | |
| // native wheel, run curl|sh against their own release host, set soffice | |
| // LD_PRELOAD for document conversion, or point pip/npm at a corporate mirror. | |
| // These benign-but-scary idioms share surface features with attacks; the | |
| // allowlist downgrades matching findings so the scanner is not an FP cannon that | |
| // forces operators to globally suppress bundle_cross_file. | |
| // | |
| // SAFETY INVARIANT: the defanged exfil host (attacker.example, 198.51.100.0/24 | |
| // TEST-NET) is NEVER allowlisted, so a downgrade can never launder a real exfil | |
| // signal. | |
| // knownBenignHosts are package-registry/CDN hosts treated as legitimate mirror | |
| // targets. A registry rewrite to one of these is a corporate-mirror idiom, not | |
| // an attack. Matching is host-suffix aware (registry.corp.example matches an | |
| // entry "corp.example") via isKnownBenignHost. | |
| var knownBenignHosts = map[string]bool{ | |
| // Default public registries (rewriting "to" the default is a no-op, but the | |
| // string still appears in legit configs). | |
| "registry.npmjs.org": true, | |
| "pypi.org": true, | |
| "files.pythonhosted.org": true, | |
| "rubygems.org": true, | |
| "crates.io": true, | |
| "static.crates.io": true, | |
| "proxy.golang.org": true, | |
| "sum.golang.org": true, | |
| "repo.maven.apache.org": true, | |
| "repo1.maven.org": true, | |
| // Common trusted release/CDN hosts skills legitimately curl from. | |
| "github.com": true, | |
| "raw.githubusercontent.com": true, | |
| "objects.githubusercontent.com": true, | |
| "codeload.github.com": true, | |
| "registry.yarnpkg.com": true, | |
| "cdn.jsdelivr.net": true, | |
| "unpkg.com": true, | |
| } | |
| // benignHostSuffixes are corporate/enterprise mirror domains that legitimately | |
| // host private registries. A rewrite to a sub-host of one of these is a | |
| // known-benign corporate-mirror pattern. | |
| var benignHostSuffixes = []string{ | |
| "jfrog.io", | |
| "artifactory.com", | |
| "pkg.dev", // Google Artifact Registry | |
| "azure.com", // Azure Artifacts | |
| "visualstudio.com", | |
| "nexus.example", // documented corporate-mirror placeholder in the corpus | |
| "corp.example", // documented corporate-mirror placeholder in the corpus | |
| } | |
| // disallowedExfilHostFragments are NEVER allowlisted regardless of any other | |
| // rule (defense against an allowlist entry accidentally covering the corpus's | |
| // defanged exfil host). | |
| var disallowedExfilHostFragments = []string{ | |
| "attacker.example", | |
| "198.51.100.", | |
| } | |
| // isKnownBenignHost reports whether host (already lowercased, no scheme/path) is | |
| // a recognized legitimate registry/CDN/corporate-mirror. The defanged exfil host | |
| // is explicitly excluded. | |
| func isKnownBenignHost(host string) bool { | |
| host = strings.ToLower(strings.TrimSpace(host)) | |
| if host == "" { | |
| return false | |
| } | |
| // Strip a port if present. | |
| if i := strings.IndexByte(host, ':'); i >= 0 { | |
| host = host[:i] | |
| } | |
| for _, frag := range disallowedExfilHostFragments { | |
| if strings.Contains(host, frag) { | |
| return false | |
| } | |
| } | |
| if knownBenignHosts[host] { | |
| return true | |
| } | |
| for _, suf := range benignHostSuffixes { | |
| if host == suf || strings.HasSuffix(host, "."+suf) { | |
| return true | |
| } | |
| } | |
| return false | |
| } | |
| // internalHostSuffixes are multi-label corporate/private-network suffixes that | |
| // indicate an internal mirror rather than a public exfil host. A registry | |
| // rewrite pointing at one of these is the enterprise "configure the internal | |
| // cache" idiom (e.g. pypi.internal.example.com, registry.internal.example.com) | |
| // and must not corroborate on its own. | |
| var internalHostSuffixes = []string{ | |
| ".internal", // bare *.internal | |
| ".intra", // *.intra | |
| ".corp", // *.corp | |
| ".lan", // *.lan | |
| ".home.arpa", // RFC 8375 home-network reserved zone | |
| ".localdomain", // common single-host local suffix | |
| } | |
| // internalHostInfixes are multi-label internal markers that appear as an inner | |
| // label rather than a trailing suffix, e.g. "internal" in | |
| // pypi.internal.example.com / registry.internal.example.com (the corpus's | |
| // corporate-mirror placeholder). Matched as a dot-delimited label so a host like | |
| // "internalattacker.example" does NOT match. | |
| var internalHostInfixes = []string{ | |
| ".internal.", | |
| ".intra.", | |
| ".corp.", | |
| } | |
| // isInternalRegistryHost reports whether host (lowercased, no scheme/path) is an | |
| // internal/private registry mirror destination: an RFC1918 / loopback IP, a | |
| // localhost name, or a host under an internal/corp suffix or infix. A rewrite to | |
| // such a host is a benign enterprise mirror idiom and must NOT corroborate. | |
| // | |
| // SAFETY: the defanged exfil host (attacker.example, 198.51.100.0/24 TEST-NET) | |
| // is checked FIRST and can never be classified as internal, so a real exfil | |
| // signal can never be laundered through this downgrade. | |
| func isInternalRegistryHost(host string) bool { | |
| host = strings.ToLower(strings.TrimSpace(host)) | |
| if host == "" { | |
| return false | |
| } | |
| // Unwrap a bracketed IPv6 literal, optionally followed by ":port". | |
| if strings.HasPrefix(host, "[") { | |
| if end := strings.IndexByte(host, ']'); end >= 0 { | |
| host = host[1:end] | |
| } else { | |
| host = strings.TrimLeft(host, "[") | |
| } | |
| } else if strings.Count(host, ":") == 1 { | |
| // A single colon is a host:port separator (bare IPv6 has >=2 colons). | |
| host = host[:strings.IndexByte(host, ':')] | |
| } | |
| if host == "" { | |
| return false | |
| } | |
| // SAFETY GUARD FIRST: never downgrade the defanged exfil host. | |
| for _, frag := range disallowedExfilHostFragments { | |
| if strings.Contains(host, frag) { | |
| return false | |
| } | |
| } | |
| // Loopback / localhost names and literals. | |
| if host == "localhost" || host == "127.0.0.1" || host == "::1" || | |
| strings.HasSuffix(host, ".localhost") { | |
| return true | |
| } | |
| // RFC1918 private IPv4 ranges. | |
| if isPrivateIPv4(host) { | |
| return true | |
| } | |
| // Internal/corp multi-label suffixes (trailing). | |
| for _, suf := range internalHostSuffixes { | |
| if strings.HasSuffix(host, suf) { | |
| return true | |
| } | |
| } | |
| // Internal/corp markers as an inner label (e.g. *.internal.example.com). | |
| for _, inf := range internalHostInfixes { | |
| if strings.Contains(host, inf) { | |
| return true | |
| } | |
| } | |
| return false | |
| } | |
| // isPrivateIPv4 reports whether host is a dotted-quad in an RFC1918 private | |
| // range: 10.0.0.0/8, 172.16.0.0/12, or 192.168.0.0/16. Non-IP hosts return | |
| // false. | |
| func isPrivateIPv4(host string) bool { | |
| parts := strings.Split(host, ".") | |
| if len(parts) != 4 { | |
| return false | |
| } | |
| octets := make([]int, 4) | |
| for i, p := range parts { | |
| if p == "" || len(p) > 3 { | |
| return false | |
| } | |
| n := 0 | |
| for _, c := range p { | |
| if c < '0' || c > '9' { | |
| return false | |
| } | |
| n = n*10 + int(c-'0') | |
| } | |
| if n > 255 { | |
| return false | |
| } | |
| octets[i] = n | |
| } | |
| switch { | |
| case octets[0] == 10: | |
| return true | |
| case octets[0] == 172 && octets[1] >= 16 && octets[1] <= 31: | |
| return true | |
| case octets[0] == 192 && octets[1] == 168: | |
| return true | |
| } | |
| return false | |
| } | |
| // isKnownBenignNativePattern reports whether a native binary file matches a | |
| // common legitimate shipping pattern (a signed/platform-tagged native wheel or | |
| // a Node native addon) so a bare .so does not self-escalate. This is a | |
| // precision lever only: a binary that embeds the exfil host or dangerous | |
| // symbols is handled by the analyzer BEFORE this downgrade is consulted. | |
| func isKnownBenignNativePattern(f *File) bool { | |
| if f == nil { | |
| return false | |
| } | |
| base := strings.ToLower(filepath.Base(f.RelPath)) | |
| // Node native addon convention. | |
| if strings.HasSuffix(base, ".node") { | |
| return true | |
| } | |
| // Python extension modules carry an ABI tag, e.g. | |
| // "_speedups.cpython-312-x86_64-linux-gnu.so" or "...-darwin.so". | |
| if strings.HasSuffix(base, ".so") || strings.HasSuffix(base, ".dylib") { | |
| if strings.Contains(base, ".cpython-") || | |
| strings.Contains(base, ".abi3.") || | |
| strings.Contains(base, "-x86_64-") || | |
| strings.Contains(base, "-aarch64-") || | |
| strings.Contains(base, "-arm64-") || | |
| strings.Contains(base, "-darwin") || | |
| strings.Contains(base, "-linux-gnu") { | |
| return true | |
| } | |
| } | |
| return false | |
| } | |
| // isKnownBenignScriptIdiom reports whether a script line matches a benign-but- | |
| // scary idiom that should not, on its own, escalate. Examples: setting | |
| // LD_PRELOAD for soffice/libreoffice document conversion, or rustup/nvm-style | |
| // installers fetching from their own canonical host. Lines containing the | |
| // defanged exfil host are never benign. | |
| func isKnownBenignScriptIdiom(line string) bool { | |
| low := strings.ToLower(line) | |
| for _, frag := range disallowedExfilHostFragments { | |
| if strings.Contains(low, frag) { | |
| return false | |
| } | |
| } | |
| // soffice/libreoffice LD_PRELOAD doc-conversion idiom. | |
| if strings.Contains(low, "ld_preload") && | |
| (strings.Contains(low, "soffice") || strings.Contains(low, "libreoffice") || | |
| strings.Contains(low, "unoconv")) { | |
| return true | |
| } | |
| // Canonical first-party installers fetched from their own hosts. | |
| benignInstallerHosts := []string{ | |
| "sh.rustup.rs", "static.rust-lang.org", | |
| "raw.githubusercontent.com/nvm-sh", | |
| "get.docker.com", "deb.nodesource.com", "rpm.nodesource.com", | |
| "install.python-poetry.org", | |
| } | |
| if matchedAny(low, benignInstallerHosts) { | |
| return true | |
| } | |
| return false | |
| } | |