#11264·trivy

fix(dpkg): build the dependency graph for packages from /var/lib/dpkg/status.d

Author: DmitriyLewenCreated Sep 17, 2026Updated Sep 17, 2026
Labelskind/bug

Description

In images that store dpkg metadata in /var/lib/dpkg/status.d/ (one file per package, e.g. distroless), all OS packages have an empty DependsOn. As a result, the SBOM shows every package as a direct dependency of the OS.

Reproduced on v0.74.0:

bash
trivy image --format cyclonedx --output base.cdx.json gcr.io/distroless/base-debian12

libssl3 is expected to depend on libc6, but its dependsOn is empty. If the files from status.d/ are merged into a single /var/lib/dpkg/status, the graph is built correctly.

Discussed in #11260

Root cause

parseDpkgStatus is called separately for each status file, and consolidateDependencies resolves names from Depends into IDs (name@version) only against packages from the same file. Names that aren't found are dropped. status holds all packages in one file, while each file in status.d/ holds exactly one package, so dependencies are never found.

Implementation challenges

1. Packages live in different layers

In distroless images each package usually sits in its own layer. For example, libc6 and libssl3 are in different layers of gcr.io/distroless/base-debian12. The analyzer (including PostAnalyze) works on a single layer and doesn't see packages from other layers.

So resolving across all status.d/ files inside PostAnalyze would only fix single-layer images and rootfs/fs, not real distroless images. Dependencies have to be linked after the layers are merged, i.e. in ApplyLayers (pkg/fanal/applier/docker.go) or later.

2. The analyzer has to keep unresolved dependencies

Today the analyzer drops names that aren't found in the file, so they never reach the applier. The analyzer therefore has to keep them, and we need to decide where to store them:

  • In the existing DependsOn. No changes to types.Package or protobuf, but the field gets a dual meaning: package names in the cache and RPC, IDs in the report. Debian package names can't contain @, so a name can be told apart from an ID, but this is an implicit contract.
  • In a new field. The meaning is explicit, but it changes types.Package, rpc/common/service.proto and the converters in pkg/rpc/convert.go. The field has to be cleared after resolution, otherwise it leaks into the JSON report.

3. Where and in which order to resolve

  • ApplyLayers already has dpkg-specific logic (merging licenses), so there is a precedent. The alternative is pkg/scan/local, but then ApplyLayers would return an incomplete result.
  • Resolution must only touch packages found by the dpkg analyzer (AnalyzedBy). Packages from embedded SBOMs (AnalyzedBy: sbom) already have IDs in DependsOn and must stay unchanged.
  • We need to define the order relative to filterMismatchedOSPkgs and package de-duplication (#8298), so that the name → ID map is built from the final package list.
  • Names that still can't be resolved (package not installed, virtual package) have to be dropped, so that DependsOn in the report keeps containing only IDs.

4. Ambiguous name → ID mapping

  • The same package can be present in several status.d/ files with different file names and in different layers (#8273, #8298). If the versions match, it's a single ID, but if they differ, we need a rule: the last layer wins, all versions, or something else.
  • Multi-arch: libc6:amd64 and libc6:i386 share the same name, and the ID doesn't include the architecture. The current code doesn't distinguish them either, but the expected behavior should be stated.

5. Cache

Analysis results are cached per layer. Without bumping analyzerVersion, already cached layers stay without dependencies, and the fix will look like it doesn't work. Bumping the version triggers re-analysis of images for all users.

6. Client/server

The analyzer runs on the client, while ApplyLayers runs on the server. With mismatched versions (a new client and an old server), unresolved package names may end up in DependsOn in the report (with the existing-DependsOn option). Client/server version compatibility is outside the compatibility policy, but this behavior is worth keeping in mind when choosing the approach.

7. Tests

Expectations in pkg/fanal/analyzer/pkg/dpkg/dpkg_test.go and pkg/fanal/applier/docker_test.go will change, and most likely the integration test golden files too (integration/testdata/distroless-base.json.golden and SBOM outputs).

Before implementation

Before implementing, the approach (including items 2–4) should be discussed and agreed on in this issue, and only then should the implementation start.