Skip to content

v0.22.0 — absences that were guaranteed before we looked

Choose a tag to compare

@cwayne18 cwayne18 released this 21 Sep 17:10
· 11 commits to main since this release
714340b

One correctness fix to the mined-symbol layer, plus the documentation split. The fix removes wrong answers; it adds no new rule-outs.

Refuse a mined name whose absence was guaranteed (#33)

The mined-symbol layer had two gates: the name must appear literally in the advisory text, and it must share a namespace with something the package exports. Both say where the name came from. Neither says the name is a function this build would have exported, and that is the premise the whole absence test rests on.

A namespace is shared by far more than its functions. OSSL_CMP_CTX is a struct tag, OPENSSL_NO_COMP_ALG is a build macro, ASN1_TYPE is a type, SSL_OP_NO_RX_CERTIFICATE_COMPRESSION is an option constant. None of them is in any build's symbol table, of any version, ever — so their absence was guaranteed before the image was opened, and the layer was reading it as a fact about which version was compiled.

The same hole swallows a name exported under a different decoration. PCRE2 ships one library per code-unit width and suffixes every export, so an advisory written about pcre2_compile is absent from libpcre2-8-0 — which exports pcre2_compile_8 — no matter what is installed.

A third gate refuses both. A name with no lower-case letter is refused, because C spells macros, constants and type names in upper case; and an ABI-width suffix is normalised off both sides before comparing. A refused hint is inert — recorded as an observation naming the symbol and the reason, like every other validation failure. The static cgo discharge inherits the fix, since it consumes the same validated set.

How it was found

By running a real miner — claude -p, via --llm-command — against Rancher's RKE2 v1.36.0 images and auditing every rule-out it produced. Eleven package rows across four images were cleared on the strength of a struct tag, a build macro, an option constant, a type name or a code-unit suffix:

image advisory rows cleared mined name
klipper-lb:v0.4.16 CVE-2026-63074 libcrypto3, libssl3 OSSL_CMP_CTX
klipper-helm:v0.9.17 CVE-2026-63074 libcrypto3, libssl3 OSSL_CMP_CTX
hubble-ui:v0.13.3 CVE-2025-66199 libcrypto3, libssl3 OPENSSL_NO_COMP_ALG, SSL_OP_NO_RX_CERTIFICATE_COMPRESSION
hubble-ui:v0.13.3 CVE-2026-63074 libcrypto3, libssl3 OSSL_CMP_CTX
hubble-ui:v0.13.3 CVE-2026-22795 libcrypto3 ASN1_TYPE
hubble-ui:v0.13.3 CVE-2026-28388 libcrypto3 X509_V_FLAG_USE_DELTAS
hardened-calico:v3.32.0 SUSE-SU-2026:4241-1 libpcre2-8-0 pcre2_compile_32, pcre2_dfa_match, pcre2_jit_match

Three of those advisories were cleared on every row they appeared on and left their reports entirely. Two more survived only because a sibling package row happened to stay open — luck, not a guard.

Re-measured with the gate in place

Same four images, same miner. All eleven rows come back and nothing else moves:

image open OS findings dynsym rule-outs
klipper-lb 42 → 44 8 → 6
klipper-helm 57 → 59 8 → 6
hubble-ui 93 → 99 14 → 8
hardened-calico 44 → 45 1 → 0

Every rule-out resting on a lower-case-bearing name with no decorated twin survives untouched: BIO_free, OPENSSL_malloc, d2i_PKCS7/d2i_X509, BIO_vsnprintf, ERR_raise_data, BIO_f_linebuffer, OPENSSL_uni2utf8, OSSL_CMP_get1_rootCaKeyUpdate, OSSL_CMP_SRV_process_request.

The cost is that a genuinely all-caps export — MD5, SHA1 — can no longer be ruled out. A lost conclusion rather than a wrong one, which is the trade this layer is supposed to make.

This is the gate Java already had: a mined class name must be a dotted name whose last segment is capitalised, "because there is no doLookup.class and concluding absence from a method name's absence would be a plain lie." The OS path was missing the equivalent.

Not included

26 of the 31 original rule-outs tested a libcrypto symbol against the libssl3 row. Those rule-outs are true and the CVE survives on the sibling row, so nothing is hidden — but attributing an advisory's symbols to the right package row is a separate question and a separate change.

Split the README into a Docusaurus site (#31)

The README had grown to roughly 3,300 lines and served as a full reference manual. It is now a concise overview, with the deep material under docs/ and published to GitHub Pages.

Add a remediation view to the HTML dashboard (#32)


This release rules out no additional CVEs. Every change on the mined-symbol path removes an answer that should never have been given.