# How OSV-Scanner Enables Layer-Aware Scanning for Container Images

> Discover how OSV-Scanner performs layer-aware scanning for container images. Learn how its integration with Scalibr identifies vulnerabilities within specific image layers for enhanced security.

- Repository: [Google/osv-scanner](https://github.com/google/osv-scanner)
- Tags: how-to-guide
- Published: 2026-04-25

---

**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`](https://github.com/google/osv-scanner/blob/main/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.

```go
// 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.

```go
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`](https://github.com/google/osv-scanner/blob/main/internal/output/output_result.go)**, the function `processPackage` checks `vulnPkg.Package.ImageOrigin` and, when present, populates a `PackageContainerInfo` struct that stores the layer index.

```go
// 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.

```go
// 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`](https://github.com/google/osv-scanner/blob/main/internal/output/vertical.go)** displays a dedicated line for each layer, showing the layer command and the number of vulnerabilities found in that layer.

```go
// 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:

```bash

# 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:

```bash
osv-scanner container nginx:latest --format json > scan.json

```

The generated JSON contains the `image_metadata` field with layer provenance details:

```json
{
  "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/image` artifact, which extracts package origins during container analysis.
- **Metadata Flow:** Layer information flows from Scalibr's protobuf output through `ContainerImageMetadata` into OSV-Scanner's internal `models.ImageMetadata` structures.
- **Package Mapping:** The `processPackage` function in [`internal/output/output_result.go`](https://github.com/google/osv-scanner/blob/main/internal/output/output_result.go) links each package to its originating layer index via the `ImageOrigin` field.
- **Layer Aggregation:** `buildLayers` computes 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`](https://github.com/google/osv-scanner/blob/main/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`](https://github.com/google/osv-scanner/blob/main/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.