File size: 9,273 Bytes
d2507b5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 | 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
}
|