How OSV-Scanner Enables Layer-Aware Scanning for Container Images
TLDR: OSV-Scanner reports vulnerabilities per container layer by integrating with the Scalibr library to extract layer metadata and associating each discovered package with its specific originating layer index.
Layer-aware scanning pinpoints exactly which Dockerfile command introduced a vulnerability. The google/osv-scanner tool achieves this capability through tight integration with the Scalibr extraction engine, which captures per-layer provenance metadata as it analyzes container images.
Initiating Layer-Aware Container Scans
The high-level entry point for container analysis is DoContainerScan in pkg/osvscanner/osvscanner.go. This function handles image acquisition—either loading a local tarball or exporting a Docker image via imagehelpers.ExportDockerImage—before invoking the Scalibr scanner.
The scanner is configured with the layerscanning/image artifact (import "github.com/google/osv-scalibr/artifact/image/layerscanning/image"), instructing Scalibr to collect layer-by-layer information during package extraction.
// DoContainerScan excerpt from pkg/osvscanner/osvscanner.go
scanner := scalibr.New()
scalibrSR, err := scanner.ScanContainer(context.Background(), img, &scalibr.ScanConfig{
Plugins: plugins,
Capabilities: capabilities,
StoreAbsolutePath: true,
ExplicitPlugins: true,
})
Extracting Layer Metadata from Scalibr
After the scan completes, OSV-Scanner serializes the Scalibr result to a protobuf using proto.ScanResultToProto. When the protobuf contains ContainerImageMetadata, it is stored in scanResults.ImageMetadata for downstream processing.
pssr, err := proto.ScanResultToProto(scalibrSR)
if pssr.GetInventory().GetContainerImageMetadata().Len() > 0 {
scanResults.ImageMetadata = pssr.GetInventory().GetContainerImageMetadata()[0]
}
The layer information originates from Scalibr's image/layerscanning package, which reports each layer's diff ID, command, empty-layer flag, and the index of the base image it originated from.
Mapping Packages to Originating Layers
Each package discovered by Scalibr can carry an ImageOrigin field defined in models.ImageOriginDetails. In internal/output/output_result.go, the function processPackage checks vulnPkg.Package.ImageOrigin and, when present, populates a PackageContainerInfo struct that stores the layer index.
// processPackage excerpt from internal/output/output_result.go
if vulnPkg.Package.ImageOrigin != nil {
packageResult.LayerDetail = PackageContainerInfo{
LayerIndex: vulnPkg.Package.ImageOrigin.Index,
}
}
The layer index is subsequently used by buildLayers and buildBaseImages to aggregate per-layer vulnerability counts and construct the full LayerMetadata associations.
// buildLayers excerpt from internal/output/output_result.go
allLayers[i] = LayerInfo{
Index: i,
LayerMetadata: layer,
}
Rendering Layer-Aware Results
The vertical formatter in internal/output/vertical.go displays a dedicated line for each layer, showing the layer command and the number of vulnerabilities found in that layer.
// printVerticalPackageContainerInfo excerpt from internal/output/vertical.go
layerCommand := formatLayerCommand(layer.LayerMetadata.Command)[0]
fmt.Fprintf(out, " %s", text.FgCyan.Sprintf("Layer %d", layer.Index))
fmt.Fprintf(out, "%s", text.Italic.Sprintf(" %s", layerCommand))
if layer.Count.AnalysisCount.Regular > 0 {
fmt.Fprintf(out, " %s\n", text.FgRed.Sprintf("(%d vulns)", layer.Count))
}
When using the default table output format, the same data appears in a column titled "# N Layer", providing a condensed view of layer-specific vulnerability distributions.
Practical Layer-Aware Scanning Examples
Scanning a Docker Image with Per-Layer Output
Run OSV-Scanner against a container image to see vulnerabilities grouped by the layer that introduced them:
# Ensure the image is available locally
docker pull nginx:latest
# Execute layer-aware scan
osv-scanner container nginx:latest
Typical output identifies which specific Dockerfile instructions introduced vulnerabilities:
Layer 0 /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon off;"]
(3 vulns)
Layer 1 /bin/sh -c #(nop) EXPOSE 80/tcp
(0 vulns)
Exporting Raw Layer Metadata via JSON
For programmatic processing, export the scan results as JSON to access the complete layer metadata structure:
osv-scanner container nginx:latest --format json > scan.json
The generated JSON contains the image_metadata field with layer provenance details:
{
"image_metadata": {
"os": "Debian GNU/Linux 10 (buster)",
"layer_metadata": [
{
"diff_id": "sha256:...",
"command": "/bin/sh -c #(nop) CMD [\"nginx\" \"-g\" \"daemon off;\"]",
"is_empty": false,
"base_image_index": 0
}
],
"base_images": []
}
}
You can correlate specific vulnerabilities—referenced by their layer_index—with the corresponding layer_metadata entries to trace the exact command that installed the vulnerable component.
Summary
- Scalibr Integration: OSV-Scanner enables layer-aware scanning by configuring Scalibr with the
layerscanning/imageartifact, which extracts package origins during container analysis. - Metadata Flow: Layer information flows from Scalibr's protobuf output through
ContainerImageMetadatainto OSV-Scanner's internalmodels.ImageMetadatastructures. - Package Mapping: The
processPackagefunction ininternal/output/output_result.golinks each package to its originating layer index via theImageOriginfield. - Layer Aggregation:
buildLayerscomputes per-layer vulnerability counts, enabling both vertical and table formatters to display layer-specific security posture.
Frequently Asked Questions
What specific metadata does OSV-Scanner capture for each container layer?
According to the Scalibr integration in pkg/osvscanner/osvscanner.go, OSV-Scanner captures each layer's diff ID (content hash), the shell command that created the layer, a boolean indicating if the layer is empty, and the base image index identifying which parent image the layer originated from.
How does OSV-Scanner determine which layer introduced a specific vulnerability?
During results processing in internal/output/output_result.go, the processPackage function checks the ImageOrigin field on each package returned by Scalibr. This field contains a layer index that directly maps the package—and its associated vulnerabilities—to the specific layer in the container image history where it was first installed.
Can I export the raw layer metadata for custom analysis?
Yes. When using the --format json flag, OSV-Scanner includes an image_metadata object in the output that contains the complete layer_metadata array with diff IDs, commands, and base image references. You can parse this JSON to correlate the layer_index values found in vulnerability reports with the full layer provenance details.
Does layer-aware scanning work with both Docker daemon images and local tarballs?
Yes. The DoContainerScan function supports multiple input sources. It can export images from the Docker daemon using imagehelpers.ExportDockerImage or load local tarballs directly via image.FromTarball, with layer metadata extraction functioning identically regardless of the image source format.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →