So, the eternal promise of SCA tools is a complete, unambiguous bill of materials, right? We point them at our code, they divine the truth from the dependency lockfiles and manifestos we so carefully provide, and spit out a list of vulnerabilities. It's a comforting fiction, particularly in the Go ecosystem, which prides itself on simplicity and explicit dependencies. Yet here I am, having just watched a `go.mod` file sail through a standard Black Duck scan clean as a whistle, only to later find a nasty little CVE lurking in a transitive dependency that was *never in the `go.mod` in the first place*. The audacity.
The culprit, as it so often is, was the mismatch between how Go actually fetches code and what a superficial scanner chooses to see. I ran the bog-standard "Signature Scan" on the codebase. It dutifully parsed the `go.mod`, identified the direct dependencies, and called it a day. What it completely missed were the packages imported directly from version control repositories that aren't listed as module dependenciesβthink private/internal libraries or those lovely `replace` directives you use to monkey-patch a forked repo. Even more insidious, it glossed over the actual source code of the dependencies themselves. A vulnerability in a *specific function* within a vended package, not reflected in the module version, can be utterly invisible.
The "solution," of course, is to use the "Binary Analysis" or "Snippet Scan" type on the *entire* module cache or the built binary. This forces the tool to actually look at the code sitting in `$GOPATH/pkg/mod`, which is the only place you'll find the real, complete dependency tree that `go build` actually uses. The difference in output was staggering. Suddenly, a whole shadowy network of indirect and replaced dependencies appeared, complete with their own vulnerability baggage. It feels like a fundamental failure of defaults. Why would the default scan type for a Go project not mirror the actual compilation model of the language? It's like checking the packing list for a shipped container instead of x-raying the container itself.
This whole dance underscores a broader, more sardonic point about our obsession with tooling over understanding. We integrate these massive, expensive suites into our CI/CD pipelines, promise the security team we're "covered," and then blissfully ignore the fact that the scanner's mental model of a project can be radically different from the compiler's. The assumption that a dependency manifest is the single source of truth is a dangerous one, particularly in Go where the `vendor` directory and the module cache hold the real keys to the kingdom. So much for deterministic builds, eh? You get to choose which illusion of security you prefer: the clean report from a shallow scan, or the chaotic truth from a deep one. Either way, the vulnerability was there all along, laughing at you. 🤷
π€·
Exactly right. The "Signature Scan" is fundamentally a pattern matcher against known library fingerprints, and it treats the go.mod as the canonical source of truth. That approach falls apart the moment Go's actual resolution behavior deviates from that static list.
Your point about `replace` directives is the critical one. When you use `replace`, you're telling the go tool to swap one module path for another, often a local directory or a different VCS endpoint. A signature scan looking only at the original, declared module path in go.mod will never see the actual code being compiled. It's scanning a ghost.
This gets even messier with private modules fetched via GOPROXY configuration or direct VCS pulls. The scanner's knowledge graph simply doesn't have the context of your specific go workspace or private proxy setup. You end up with a clean bill of materials for dependencies that aren't the ones in your binary.
The workaround, of course, is to force a "non-deep" or "dependency" scan on the *vendored* source or the built binary itself, but that introduces its own set of timing and pipeline headaches. It feels like we're using a map of a different city to navigate our own streets.