Scanning Container Images

kunnus sbom container generates an SBOM for a container image and records, for every component, which image layer introduced it. Alias: kunnus sbom image.

shell
kunnus sbom container alpine:3.20 --output alpine.cdx.json   # registry pull
kunnus sbom container ./image.tar --output image.cdx.json    # docker save / OCI tarball
kunnus sbom container myapp:dev --source docker              # local docker daemon

Image sources

--source controls where the image comes from: auto (default — kunnus picks based on the argument), remote (pull from a registry), tarball (a docker-save or OCI archive on disk), or docker (the local daemon). A tarball scan is fully offline; a remote reference is the one case where this mode touches the network.

Per-layer attribution

Every component carries kunnus:layer:* properties: the index and DiffID of the layer that introduced it, the Dockerfile instruction that produced that layer, and whether it belongs to a detected base image. A package present in several layers lists all of them. That answers the two questions auditors actually ask: did this come from our build or from the base image, and which build step added it?

What container scans include

Container scans run the installed-state extractors — OS packages of every supported Linux family, installed language packages (wheels, gems, JARs, node_modules, Go/.NET binaries), and the non-packaged-binary classifier. Declared-dependency files like a stray package.json are deliberately not treated as installed software. The kernel extractors are host-only and never run here: a container uses the host's kernel, so claiming one in its SBOM would be wrong.

Distroless and chiselled images

Chiselled Ubuntu images (Canonical's distroless variant) have no dpkg database — their package record is the chisel manifest at var/lib/chisel/manifest.wall, which kunnus reads as its own distro family. On top of the packages themselves, the manifest's per-package SHA-256 digests are recovered and attached as component hashes, and the DEP-5 copyright files each package's copyright slice ships supply the licences. A distroless image therefore produces a fully populated SBOM rather than an empty one.

A registry reference also carries its own identity: ghcr.io/acme/app:v1.2.3 keys the SBOM's serial number by repository path with the tag as the component version, so successive builds pushed to one tag form a document series without any extra flags. Tarball scans have no such identity — see the SBOM identity guide.

For per-component property semantics see docs/sbom-properties.md in the repository.