Repository navigation
v0.19.0 — the airgap bundle, scanned where it landed
The machine that needs the answer has no route to a registry
hauler packs images, charts and files into one portable archive — a haul — so a fleet can cross an airgap as a single file. --haul scans one, and nothing is pulled:
vexscan --haul rke2-airgap.tar.zst --all --format summary
vexscan --haul haul.tar.zst --cves CVE-2024-45337 --fail-on high
vexscan --haul ./store --all --format json --out haul.jsonInside, a haul is an OCI image layout, which is a transport skopeo already speaks, so every image in one is extracted and analysed straight out of the archive. That is the whole point of the flag rather than a line in the --images-from documentation: reading a haul only to recover a list of references and then pulling them would need exactly the network the haul exists to do without.
A .tar.zst, a plain tar, or an already-unpacked store directory all work, and the compression is sniffed from the bytes rather than the file name. An archive is unpacked in full first — zstd is a stream, and there is no seeking into one for the four blobs a particular image needs — so budget disk for a second copy of the haul for the length of the run, or point --haul at a store/ directory you already have. The unpack is removed as soon as the last image has been read, before anything is rendered.
What it refuses to skip quietly
A haul carries charts and files next to its images, and vexscan scans neither. Filtering the index down to images and printing a clean report would produce something indistinguishable from a scan of a haul that had no charts in it, so everything skipped is counted and named on stderr before the scan starts:
Haul rke2-airgap.tar.zst: 2 image(s) of 4 artifact(s)
warning: 1 chart in this haul is not scanned; vexscan has no chart target.
If they were added with add-images, their images are in the haul and are covered above.
If they were not, their images are not in this haul and nothing here covers them.
hauler/rancher:2.8.2
warning: 1 artifact in this haul could not be classified and is not scanned.
If any of them is an image, this scan is missing it:
hauler/mystery:1: unrecognised config media type application/vnd.example.future.v1+json
Per category, not as one number, because the answers differ. add-images: true makes hauler resolve a chart's images into the store when it builds the bundle, so those images are in the haul and are already in the scan above; without it, they were never collected and no scan of this file can reach them. An entry this reader cannot classify gets the loudest warning of the set, with its reason, because on a haul written by a newer hauler it could be an image in a shape vexscan has not seen.
The reference comes out of the store short
hauler parses store names with an empty default registry on purpose, so org.opencontainers.image.ref.name reads rancher/hardened-kubernetes:v1.31.0 for an image whose real name carries docker.io. That is not cosmetic: the reference names the target in the report, becomes the product purl in any VEX document written from the scan, and is what a hub index is keyed on. Scanning under the stripped name would file a haul's findings somewhere a --image scan of the same image would never look.
The full name is recovered from hauler's own hauler.dev/original-ref, falling back to io.containerd.image.name. Some hauls record neither — anything built by v1, and a v2 window that lost the containerd name on the OCI import path — and those are scanned under the registryless name, loudly:
warning: 1 image in this haul has no fully qualified reference recorded, so it is scanned under the
registryless name hauler stored. Findings for it will not line up with a --image scan,
and --vex-out would file it under a different product purl:
rancher/klipper-helm:v0.9.4
Nothing is invented. Guessing docker.io for a registryless name is how an image from a private registry ends up filed under the wrong product.
The index that is incomplete on purpose
hauler-index.json is read in preference to index.json wherever it exists. hauler store save --containerd writes a deliberately incomplete index.json — non-image artifacts filtered out so containerd can import the tarball directly — and preserves the real one in that sidecar, which hauler's own store load prefers. A reader that trusted index.json on a --containerd haul would silently see fewer artifacts than are in it, on precisely the hauls most likely to be sitting in an airgap.
Hauler manifests as a list (--images-from)
The manifest that produces a haul is checked in beside the pipeline, which makes it the natural thing to scan before the bundle exists. --images-from now reads one wherever it finds one, recognised by its content.hauler.cattle.io API group so a plain reference list keeps the behaviour it had:
vexscan --images-from hauler-manifest.yaml --all --format summaryEvery document in the file is parsed, not just the first, and one that will not parse is an error rather than a skip. A manifest scan is a strict subset of a haul scan — add-images puts images in the bundle the manifest never writes down — so a chart that sets it is named individually with a pointer at --haul.
A hardening fix that came out of writing the tests
safeJoin in the unpacker had been rooting each tar entry name at / before cleaning it. That rewrites ../../etc/passwd into a harmless relative path and extracts it, which makes an archive built to escape indistinguishable from a well-formed one — and makes the check its own comment described impossible to fail. It now refuses, with . kept as the archive root that tar -cf x . writes as its first entry.
Nothing else changes
--image, --images-from, --rootfs, --repo, --rpm and --sbom behave exactly as they did in v0.18.2. --haul always produces a batch document, even for a haul holding one image, on the same grounds --images-from does: what is in a bundle is a property of the bundle, not of the command.
Full documentation: Scanning a hauler haul