Repository navigation
v0.21.0 — assertions that hold
Three changes to the path you take to discharge a taint, and to making that path work at fleet scale without a way to get a wrong answer out of it.
Block conclusions on a --roots path that does not resolve (#29)
--roots is how you answer an exec taint, usually alongside --exec-policy=assume-none. A root that named nothing was logged and dropped — the closure lost a root and reported code it would have reached as unreachable, while the same command line discharged the taint that would have withheld the conclusion.
Measured on kube-vip, a one-character typo produced 10 ruled-out findings, 7 of them wrong, in a report byte-identical in size to the correct one. Now it raises a blocking, global missing-root taint: 0 ruled out, every finding names the path.
This rules out no additional CVEs. It converts a silent wrong answer into a visible refusal.
Per-image assertions in a fleet list (#29)
The assertion flags were global — fine for one image, unusable for fifty-eight.
docker.io/rancher/mirrored-cilium-cilium:v1.16.1 exec-policy=assume-none
docker.io/rancher/hardened-coredns:v1.11.1 roots=/coredns
Keys: roots, dlopen-policy, exec-policy, dynamic-import-policy. A per-image assertion replaces the global flag for that image. Unknown keys and unparseable values are errors raised before anything is pulled.
On RKE2 v1.29.9+rke2r1, over the five images whose open base OS findings were withheld by the exec taint alone:
| image | bare open | asserted | recovered |
|---|---|---|---|
| harvester-cloud-provider:v0.2.2 | 16 | 0 | 16 |
| harvester-csi-driver:v0.1.7 | 136 | 0 | 136 |
| mirrored-cilium-cilium:v1.16.1 | 502 | 0 | 502 |
| mirrored-kube-vip-kube-vip-iptables:v0.6.0 | 22 | 0 | 22 |
| nginx-ingress-controller:v1.10.4-hardened3 | 249 | 249 | 0 |
| TOTAL | 925 | 249 | 676 (73%) |
Fleet-wide that is 33% of all open base OS findings, every one already proven unreachable. nginx-ingress recovers nothing, correctly — three taints, one assertion.
An asserted root stands down escalation (#30)
A shell entrypoint roots every program in the image, and --roots was ignored on that path. Escalation now stands down against both halves of an assertion — --roots plus --exec-policy=assume-none. Either alone still escalates, and an unresolvable root counts for neither.
| image | roots | reachable objects | proven unreached |
|---|---|---|---|
| hardened-calico:v3.28.1 | 491 → 9 | 595 → 15 of 668 | 2 → 141 of 148 |
| hardened-flannel:v0.25.6 | 546 → 15 | 663 → 38 of 825 | — |
What this release does not do, and why
The open count does not move on a Rancher hardened image. Those are cgo/FIPS Go builds, so the entrypoint is statically linked and may hold a copy of any C library, and static-elf blocks globally. On hardened-cni-plugins the closure narrows to a single reachable object and all 11 open OS findings are held by one line:
/opt/cni/bin/bridge is statically linked, so the libraries it uses are inside it and not on disk
It is discharged per-CVE by reading the entrypoint's own symbol table (#26), which needs a mined symbol.
Measurement across RKE2 v1.29.9 (58 images) found no further sound inference worth building — scoped exec recovers 11%, sharper dlopen 1%, exempting exec from presence 2% — and put the remaining reduction on running a real LLM miner rather than a stub. 766 open findings are both reached by the closure and unmined.