Skip to main content

Scanning a filesystem (--rootfs)

--rootfs DIR runs everything image mode runs, against a tree already on disk: an unpacked image, a mounted volume or snapshot, a chroot, a machine's own /. No pull, no extraction, no registry credentials.

vexscan --rootfs /mnt/rootfs --all --ecosystem os
vexscan --rootfs / --package deb:openssl --roots /usr/sbin/nginx
docker export "$(docker create myapp:latest)" | tar -x -C /tmp/rootfs
vexscan --rootfs /tmp/rootfs --all

Every ecosystem works: the package databases, the DT_NEEDED closure, the Python and npm import graphs, the jar reader, and the Go binary walk all read paths, not registries. --format inventory works the same way. The report says "mode": "rootfs" and names the directory as its target.

Nothing is deleted. The directory you name is yours; only the temporary directory image mode extracts into is ever removed.

What it costs: there is no image config​

A directory does not carry an Entrypoint, a Cmd, an env or a PATH, and vexscan does not invent one. That is the whole difference between the two modes, and it lands on the reachability tests:

EcosystemWithout a config
OS packagesthe ELF closure roots every program it finds, records the no-entrypoint taint, and keeps going — the taint is non-blocking, so not_in_execute_path is still reachable, just rarer
Python, npmno-entrypoint is a blocking taint: no not_in_execute_path at all until you supply a root
Go, Javaunaffected — neither reads the config

--roots is the remedy, and it is the same flag image mode already uses for an image whose real command comes from outside its config:

vexscan --rootfs /mnt/rootfs --all --roots /usr/bin/myapp --roots /usr/bin/worker

Name what actually runs. A root that is a wrapper script rather than a real program makes things worse, not better — see the npm measurement below.

A --roots path that names no ELF object in the image is a blocking missing-root taint, not a skipped argument. Misspell it and the closure would otherwise carry on one root short, reporting code it never looked at as unreachable — and since --roots is usually paired with --exec-policy=assume-none, there would be nothing left to withhold the conclusion. So a typo costs you every answer for that image rather than buying you wrong ones. The taint names the path that failed, and says whether it was absent or present-but-not-an-ELF-object.

Measured against the same image, both ways​

docker export of debian:12 into a directory, scanned with --rootfs, versus --image debian:12:

packagesfindingsnot_presentlinked
--image debian:12881597152
--rootfs (exported)881597152

The reports are identical except for one string: the ELF closure records its root reason as no entrypoint rather than shell entrypoint. Both escalate to rooting every program, so every conclusion matches. That is a happy case rather than a general result — debian:12 ships bash as Cmd, which was already telling the closure nothing.

node:22-slim, same comparison, --ecosystem npm: 14 findings, all linked, in both modes. The blocking taint differs (no-entrypoint versus the image's foreign-entrypoint, since docker-entrypoint.sh is not a Node script) and changes nothing, because both block.

Adding --roots /usr/local/bin/npm to the rootfs run narrows the graph from 215 roots to 1 — and still concludes nothing, because npm's launcher has no node_modules beside it, which is its own blocking taint. A root has to be the real program with its dependencies in place.

Permissions: a tree you cannot fully read​

A rootfs owned by root and scanned by someone else is the common case, and the one that matters most here. A directory the walk cannot list contributes no findings — exactly what a directory with nothing wrong in it contributes.

So every path a walk could not enter is recorded, named in both the text report and the inventory above the results, carried in the JSON as unreadable, and exits 1. A scan that could not read the tree never exits 0.

INCOMPLETE: 3 path(s) could not be read, so this report does not account for them:
/opt/vendor
/srv/data
/root

Run as root, or sudo, or fix the modes — but do not read the result as clean until that line is gone. (Image mode effectively never prints it: extraction creates every directory 0755.)

/proc, /sys and /dev are skipped rather than reported. They ship no code, and /proc alone is tens of thousands of synthetic entries that stat as regular files.