Safeguard
Open Source Security

How Snyk Container detects application-level dependencies...

A mechanical look at how Snyk Container scans image filesystems to detect npm, pip, Maven, and other application dependencies bundled inside containers.

Karan Patel
Cloud Security Engineer
7 min read

A container image is really two dependency trees stacked on top of each other. There's the OS layer — packages tracked by dpkg, rpm, or apk — and there's the application layer: the npm packages copied into node_modules, the wheels a pip install dropped into site-packages, the jars a Maven build shaded into a fat JAR. A scanner that only reads /var/lib/dpkg/status or the apk package database sees the first tree and is blind to the second. That gap mattered in practice: Log4Shell (CVE-2021-44228) shipped inside countless images as a bundled log4j-core-2.14.1.jar, with no OS package manager entry anywhere near it. Snyk Container was built to close exactly that gap by scanning application-level dependencies alongside OS packages, in the same snyk container test run. This post walks through the publicly documented mechanics of how that detection actually works, layer by layer.

Why do OS package scanners miss application dependencies in the first place?

They miss them because OS package managers only track what they installed themselves, and most application dependencies never touch a package manager at all. When a Dockerfile runs npm install or pip install -r requirements.txt, or a multi-stage build copies a compiled target/app.jar into the final image, those files land directly on the filesystem with no corresponding entry in dpkg, rpm, or apk's package database. A scanner built around apt list --installed or parsing /lib/apk/db/installed has nothing to query — the npm package is just a directory of .js files under node_modules, indistinguishable from any other file on disk unless something specifically knows to look for package.json and package-lock.json and parse them as a dependency manifest. This is the reason vulnerability reports for popular base images historically undercounted findings: they reported the Alpine or Debian CVEs correctly but said nothing about the vulnerable Spring Boot or Django app baked into the same image.

How does Snyk Container actually find application dependencies without running the container?

It statically unpacks the image's layer tarballs and walks the resulting filesystem tree looking for known dependency artifacts, the same way snyk test scans a source repository. Container images built to the OCI or Docker image spec are stored as a manifest referencing a stack of layer tarballs, each identified by a SHA256 digest and representing a filesystem diff. Snyk Container pulls the image (or reads a local tarball exported with docker save), extracts each layer in order to reconstruct the final merged filesystem, and then runs its ecosystem-specific dependency detectors against that filesystem — the same detection logic used by Snyk Open Source for source repositories, applied to whatever manifest and lockfiles it finds inside the image: package.json/package-lock.json for npm, requirements.txt/Pipfile.lock for Python, pom.xml or Gradle lockfiles for Maven/Gradle projects, Gemfile.lock for Ruby, go.mod/go.sum for Go, and .csproj/packages.lock.json for .NET. No container needs to start; nothing needs to run. The whole process is static filesystem analysis performed against the extracted layers.

How does Snyk identify a bundled JAR when there's no pom.xml anywhere in the image?

It fingerprints the jar file itself rather than relying on a manifest. This matters because production images very often contain compiled artifacts with the build metadata stripped out — a multi-stage Dockerfile might COPY --from=build /app/target/app.jar into a slim runtime image, discarding the Maven project files that named the dependency in the first place. Snyk's documented approach for this case is jar fingerprinting: it computes hashes over the jar's contents (and, for shaded or fat jars, the individual class files bundled inside it) and matches those hashes against a database Snyk maintains that maps known hash values to Maven group/artifact/version coordinates. That lets Snyk recognize, for example, that a specific log4j-core-2.14.1.jar sitting inside an image is the same artifact published to Maven Central under that coordinate, even though the image itself carries no pom.xml, no trustworthy MANIFEST.MF version string, and no build log. The same general idea — identifying compiled binaries by content hash rather than declared metadata — is what lets Snyk Container also flag vulnerable dependencies statically linked into Go binaries, using the build information the Go toolchain embeds in the compiled binary itself.

How does Snyk attribute a vulnerable dependency to the specific Dockerfile line that introduced it?

It correlates each layer's SHA256 digest against the Dockerfile instructions that produced it, when a Dockerfile is supplied alongside the image. Because every layer in an OCI image is an addressable, ordered filesystem diff, Snyk Container can determine which layer first introduced a given file — and therefore which layer introduced a given vulnerable dependency — by diffing the reconstructed filesystem layer by layer instead of only looking at the final merged state. Running snyk container test <image> --file=Dockerfile has Snyk parse the Dockerfile and map its build instructions to the resulting layers, so the CLI output can point at the specific RUN npm install or COPY package.json . line responsible for a flagged package rather than just saying "somewhere in this 400MB image." This layer-to-instruction mapping is also what powers Snyk's base-image remediation advice: separating vulnerabilities that came from the base image, such as node:18-slim, from ones introduced by application-layer instructions later in the build tells a team whether the fix is "bump the base image tag" or "update a package in your lockfile."

Which ecosystems does this cover, and where does the detection reach its limits?

Coverage spans the major application ecosystems — npm/Yarn, pip, Maven/Gradle, RubyGems, Go modules, NuGet, and Composer are all documented as supported for Snyk Container's application-dependency scanning — but the technique still depends on finding either a parseable manifest or an artifact that matches a known fingerprint. Dependencies fetched dynamically at container runtime, such as a plugin downloaded on first boot or a package pulled in by an entrypoint script rather than baked in at build time, aren't present in the static image filesystem Snyk scans, so they're invisible to a snyk container test run by definition — catching those requires monitoring the running workload instead. Vendored source code copied in without any manifest or recognizable package structure, and dependencies inside custom build artifacts that don't match a known hash, can also fall outside what fingerprinting can positively identify. Snyk's own documentation is explicit that snyk container monitor should be used alongside test to track newly disclosed vulnerabilities against images already deployed, since a scan is a snapshot of what a given image contained on the day it was tested, not a continuous guarantee.

How Safeguard Helps

Understanding how a scanner like Snyk Container reaches its conclusions matters because "the scan came back clean" is only as trustworthy as the artifact that was scanned. Safeguard focuses on the layer beneath the scan result: verifying where a container image actually came from, whether its layers match the source and build pipeline they claim to, and whether the application dependencies baked into it were pulled from the packages your organization intended to use in the first place. That means generating and verifying SBOMs and build provenance attestations for images as they move through CI/CD, flagging when a build introduces a dependency that wasn't declared in the source lockfile a scanner would expect to see, and giving security teams a policy layer that gates image promotion based on both scan results and the integrity of how the image was assembled. Application-dependency detection tells you what's inside an image; Safeguard helps confirm that what's inside is what was supposed to be there, and that the chain from source commit to running container hasn't been tampered with along the way.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.