skip to content

Why does an SBOM scan of a scratch image holding one static Go binary find almost nothing?

level: middleimportance: should knowfreq 45%

answer

  1. the scan reads labels, not code
  2. no package database, no shared objects
  3. static linking flattens the graph early
  4. empty is not the same as clean
  5. move the vantage into the build

basics

~20 s

Filesystem analysis identifies components from evidence — package databases, manifests, shared libraries. A scratch image with one statically linked binary has none of that, because every dependency was compiled into the executable, so the scan reports an almost empty document.

solid answer

~50 s

A post-build filesystem vantage does not know what your code depends on; it infers components from artefacts left lying around — an OS package database, packaged archives with embedded metadata, shared objects the loader would need. A scratch base contributes no package database, and static linking means there are no shared objects either: the dependency graph was flattened into one executable at compile time. So the honest output is near-empty, and the danger is that near-empty reads as "clean" rather than "could not see". Some ecosystems embed their own module metadata in the binary, and a language-aware analyser can recover the list — but that is a per-language special case, and statically linked C or C++ leaves no equivalent record. The general fix is to move the vantage: generate during the build, where the module graph is still known.

go deeper

for a junior

Remember that a scan of a built artifact finds components by their leftover packaging evidence. If nothing was installed by a package manager and nothing is a separate library file, there is nothing for it to find.

for a middle

Explain the mechanism concretely: no OS package database, no per-library shared objects, dependencies resolved and copied in at compile time. Then name build-time generation as the vantage that still holds the graph.

for a senior

Demonstrate that you treat an implausibly small result as an unreadable one and gate on it. Be ready to describe pairing a build-time document for the application graph with artifact analysis for the OS layer, and why neither alone is enough.

for a principal

Frame it as a platform decision with two competing goods: minimal images reduce post-exploitation options while destroying post-build visibility. Own the position that you keep the minimal image and buy the visibility back in the build pipeline.

### The setup A logistics carrier ships a routing service written in Go. It compiles to a single statically linked executable and is packaged in a scratch image — no distribution, no shell, no package manager, nothing but the binary. Someone runs a filesystem-vantage generator over the image and gets a document with essentially no components in it. The service is business-critical for availability: if routing stops, trucks stop being dispatched. The empty document is then treated as evidence of a tiny attack surface. ### Why the scan comes back empty Post-build filesystem analysis is fundamentally an *evidence-gathering* exercise. It looks for the traces that package management leaves behind: - an operating-system package database listing installed packages and versions; - packaged archives carrying their own metadata files inside them; - installed language-ecosystem package directories with manifest files; - dynamically linked shared objects, which are separate files on disk and must be present for the loader. A scratch image supplies none of the first three, because nothing was ever installed with a package manager. Static linking removes the fourth: the compiler resolved and copied the needed code into the executable at build time, so there is no separate file per library to find. Everything the service depends on is present — as bytes inside one file — and none of it is *labelled*. This is the general property worth taking away: **a filesystem vantage sees labels, not code.** Anything that ships without a label is silently absent. Static linking is one way to lose the label; a bundled or minified single-file distribution is another; source copied directly into your tree is a third. ### The failure mode that matters The defect here is not the missing entries. It is the *shape of the absence*: an SBOM cannot distinguish "I looked and there is nothing" from "I could not see". A near-empty document over a real production service is an unreadable result presented as a reassuring one, and downstream it feeds a vulnerability-matching step that dutifully reports zero findings. You have manufactured confidence rather than coverage. So when a scan returns implausibly little, the correct reaction is to ask what the artifact is made of, not to record a good result. A single-binary artifact, a bundled front-end asset, or an image with no package manager should all trigger that question. ### What actually recovers the list **Move the vantage to build time.** Inside the build the dependency graph still exists as a graph: the build system knows every module and version it resolved and compiled in. Generating there produces the real list, and because it happens in the same run that produced the binary, it can be bound to that specific artifact. This is the general answer and it works regardless of language. **Exploit language-specific embedded metadata where it exists.** Some toolchains record their module list inside the compiled binary, and an analyser that knows the layout can read it back out. That genuinely rescues the post-build vantage for those languages — but treat it as a per-ecosystem bonus, not a strategy. A statically linked C or C++ executable typically carries no such structured record, and you are reduced to fingerprinting symbols and version strings, which is heuristic and version-imprecise. **Keep the artifact-level scan anyway, for a different job.** If the image later gains a distribution base, the filesystem vantage is what covers the OS half, which build-time generation of the application graph says nothing about. The two are complementary; the mistake is expecting either alone to be complete. ### Answering this in an interview A weak answer says "scratch images are secure because they have almost nothing in them". A strong answer separates *attack surface* from *visibility*: a scratch image genuinely does reduce what an attacker can use after landing — no shell, no package manager — and that is a real benefit. But it does not reduce the amount of third-party code you compiled in, and it destroys the evidence a post-build scan relies on. You want both properties: keep the minimal image, and get the component list from the build rather than from the artifact.

  • Does a minimal image with no package manager still reduce risk, given this blind spot?
    Yes, but along a different axis. Removing the shell and package manager cuts what an attacker can do once they have execution inside the container, which is a genuine post-exploitation control. It does nothing about the volume of third-party code compiled into the binary. Visibility and attack surface are separate properties, and this choice improves one while damaging the other.
  • If the same service used a distribution base image instead, would the scan be complete?
    No. You would recover the OS package half, because the package database is back and the scan can read it, but the statically linked application dependencies are still unlabelled bytes inside the executable. You would get a plausible-looking document that is still missing the entire application graph — arguably more misleading than the near-empty one.
  • How would you stop a near-empty document from being recorded as a passing result?
    Treat implausibly low component counts as a pipeline failure rather than a success, keyed to the artifact type. A single-binary or bundled artifact that yields almost no components means the vantage could not see, so the release gate should demand a build-time document for that artifact instead of accepting the scan's silence.

Taking an inventory by reading the labels on boxes works until someone melts every box into a single solid block. The contents are all still there; the labels are gone.

saying these in an interview costs you the question

  • Reads a near-empty document as a clean result
  • Believes static linking removes the dependencies rather than embedding them
  • Thinks deeper layer traversal would recover compiled-in components
  • Confuses reduced attack surface with reduced component inventory
  • Assumes every language embeds a readable module list in its binaries

context