pkgdb

package
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 4, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package pkgdb reads the installed-package databases of the three OS package managers that show up in container images: dpkg, apk and rpm.

It answers two questions the OS ecosystem plugin needs: which packages are installed at which versions, and which files each one owns. The second is what connects a CVE against "openssl" to the ELF objects that would have to be loaded for it to matter.

Nothing here talks to OSV or to the network. Readers parse a filesystem and return what they found, or an error -- never a partial answer.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type APK

type APK struct{}

APK reads apk's database, as used by Alpine, Wolfi, Chainguard and MinimOS.

func (*APK) Detect

func (*APK) Detect(fsys target.RootFS) (string, bool)

func (*APK) Format

func (*APK) Format() Format

func (*APK) Read

func (a *APK) Read(fsys target.RootFS) ([]Package, error)

type Deb

type Deb struct{}

Deb reads dpkg's database.

func (*Deb) Detect

func (*Deb) Detect(fsys target.RootFS) (string, bool)

Detect looks for either shape of dpkg database.

Debian and Ubuntu ship one concatenated /var/lib/dpkg/status. Google's distroless images ship /var/lib/dpkg/status.d/ instead, a directory holding one status paragraph per package, because the images are assembled by Bazel rather than by dpkg. Both are real and both appear in images people scan.

func (*Deb) Format

func (*Deb) Format() Format

func (*Deb) Read

func (d *Deb) Read(fsys target.RootFS) ([]Package, error)

type Format

type Format string

Format identifies a package manager.

const (
	FormatDeb Format = "deb"
	FormatAPK Format = "apk"
	FormatRPM Format = "rpm"
)

type Package

type Package struct {
	Format Format `json:"format"`
	Name   string `json:"name"`
	// Version is the version string to compare against OSV ranges. For rpm
	// this is the full EVR with the epoch always present ("0:1.43.0-5.el9_3"),
	// because that is how the Red Hat, Rocky and AlmaLinux records are
	// written. See rpmEVR.
	Version string `json:"version"`
	Arch    string `json:"arch,omitempty"`

	// Epoch is the rpm epoch, broken out so a consumer can reconstruct the
	// epoch-free version for the ecosystems whose records omit it (Azure
	// Linux). Zero and meaningless for deb and apk.
	Epoch int `json:"epoch,omitempty"`

	// Source is the source (dpkg "Source:", apk "o:", rpm SOURCERPM) package
	// this was built from, when the database records one and it differs from
	// Name.
	Source string `json:"source,omitempty"`
	// SourceVersion is the source version when dpkg records one explicitly,
	// which it only does when it differs from Version.
	SourceVersion string `json:"source_version,omitempty"`

	// Files are the tree-absolute paths the package installs, as the database
	// records them: directories included, nothing stat'ed.
	Files []string `json:"files,omitempty"`

	// DB is the tree-absolute path of the database this came from, for
	// evidence and error messages.
	DB string `json:"db,omitempty"`
}

Package is one installed package.

func (Package) OSVNames

func (p Package) OSVNames() []string

OSVNames returns the package names to query OSV with, likeliest first.

Which name a distribution files advisories against is not consistent, and not even consistent within one package format. Verified against the live api.osv.dev, querying with no version so the count is "does this name exist in the database at all":

Debian:12      openssl 255    libssl3       0    <- source
Debian:12      glibc   158    libc6         0    <- source
Alpine:v3.19   openssl  55    libssl3       0    <- origin
Red Hat        openssl 168    openssl-libs 113   <- binary
AlmaLinux:9    openssl  10    openssl-libs  15   <- binary
Rocky Linux:9  openssl  10    openssl-libs   0   <- source, unlike its
                                                    upstream and its peer

So the rule cannot be "deb and apk use the source name, rpm uses the binary name": Rocky and AlmaLinux are both RPM rebuilds of Red Hat and disagree. Both names are returned instead. A name that matches nothing costs one entry in a batch query; choosing the wrong single name reports a vulnerable package as clean, which is the one outcome this tool must never produce.

The order is a display and tie-break preference only -- callers query every name returned.

type RPM

type RPM struct{}

RPM reads rpm's database.

func (*RPM) Detect

func (*RPM) Detect(fsys target.RootFS) (string, bool)

func (*RPM) Format

func (*RPM) Format() Format

func (*RPM) Read

func (r *RPM) Read(fsys target.RootFS) ([]Package, error)

type Reader

type Reader interface {
	// Format names the package manager this reader understands.
	Format() Format
	// Detect reports whether the tree carries this kind of database, and the
	// tree-absolute path of the one it found.
	Detect(fsys target.RootFS) (string, bool)
	// Read parses the database. A database that Detect found but Read cannot
	// parse is an error, never an empty slice.
	Read(fsys target.RootFS) ([]Package, error)
}

Reader parses one kind of package database out of a filesystem tree.

func Readers

func Readers() []Reader

Readers returns every backend, in a stable order.

type Result

type Result struct {
	Format   Format    `json:"format"`
	DB       string    `json:"db"`
	Packages []Package `json:"packages"`
}

Result is what one reader found.

func Read

func Read(fsys target.RootFS) ([]Result, error)

Read runs every reader that detects a database in fsys.

A reader whose database is present but unparseable fails the whole call. Reporting the packages from the other databases and quietly omitting that one would render as "these are all the packages in the image", and every package the unread database owns would be attested as not present.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL