Repository navigation
Bring the Rust detector set to parity with the browser library (14 detectors) - #8
Merged
Merged
Conversation
The browser detector library at bootintel.com/tools/fingerprint is the
source of truth for the detector set — it is what the web tool and the
legacy Node analyzer both run, and cli/sync-detectors.mjs already keeps
those two in step. This crate shipped 9 of its 14 detectors and folded
two of the rest into the wrong label, so the same log produced different
JSON depending on which implementation read it.
Adds Runtime firmware (OpenSBI), ROM identifier (ESP-ROM), Firmware SDK
(ESP-IDF), Userland (BusyBox) and Flash layout (MTD partition map), in
the browser's registration order, and moves OpenSBI out of Bootloader —
which is an output-schema change for anyone parsing findings by label.
On a RISC-V log that prints both, U-Boot won the old precedence chain
and the OpenSBI version disappeared from CLI output entirely.
Also aligns the machinery the two new detectors depend on:
* source/line_number are derived by re-running the detector per line
and matching value+detail, the browser's algorithm. The old
substring search could attach evidence for a match that exists on
no single line, and an aggregate finding (Flash layout counts
partitions across the whole table) correctly gets no evidence
fields at all.
* the two policy detectors read the original lines, not the
normalized ones, as the browser does.
* partition offsets are de-duplicated: bootintel-9.txt prints its
table twice and reported 14 partitions for a 7-partition device.
Proven rather than asserted: tests/browser_parity.rs runs the browser
library over all 31 corpus logs (twice each — as captured, and behind a
terminal timestamp) and diffs {label, value, detail} against this
crate's, log for log. It found four further divergences that are fixed
here: a Kernel detail stitched across line breaks, an arch claimed from
a device-family string, a PID-1 claim made from a BusyBox banner, an
init claim made from a distro greeting — plus a partition size that
rounded 1.25 MiB down where JavaScript's toFixed rounds it up.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
The browser detector library (
frontend/src/lib/detectors.tsin the website repo) is the source of truth for the detector set: it is what the web tool and the legacy Node analyzer both run, andcli/sync-detectors.mjskeeps those two in step. This crate shipped 9 of its 14 detectors, and folded one of the rest into the wrong label — so the same log produced different JSON depending on which implementation read it.This brings the Rust set to 14, in the browser's order, with the browser's labels.
New detectors
Runtime firmwareROM identifierESP-ROM:esp32-20160718)Firmware SDKUserlandFlash layoutN partitions+ named list with sizesOpenSBI x.ymoves fromBootloadertoRuntime firmware. Per the versioning policy at the top ofCHANGELOG.md, an output-schema change is MAJOR. The version is deliberately not bumped in this PR — releasing is the coordinator's job.Folding OpenSBI into
Bootloaderdid not just mislabel it, it hid it: on a board that prints both, U-Boot won the precedence chain and the OpenSBI version never reached the output at all.RISC-V / OpenSBI —
samples/bootintel-6.txt(StarFive VisionFive 2)Before:
After:
Two changes visible here:
Runtime firmware: OpenSBI 1.0appears (the browser has always reported it), and theKerneldetail stops being garbage stitched out of two unrelated lines — see Divergences found below.ESP — an ESP32 capture (
ESP-ROM:+ ESP-IDF banner; no ESP log is in the public corpus)Before:
After:
Purely additive for ESP:
Bootloader: Espressif ROM bootloaderis unchanged (the browser keeps that branch too), and the ROM stamp and SDK version arrive as their own labels.CPU / Arch: Xtensa (ESP)here comes from the literalxtensain the log, not from theesp32string — see below.Contract details this had to get right
source/line_numberare derived, not set by the detector.analyze()now re-runs each detector against each line on its own and takes the first line that reproduces the samevalue+detail, which is the browser's algorithm. The old substring search over normalized lines could attach evidence for a match that exists on no single line.evidence_is_the_original_lineasserts, over the whole corpus, that everysourceis verbatim the input line atline_number.Flash layoutis an aggregate. Itsvaluecounts partitions across many lines, so no single-line re-run reproduces it and it correctly ends up with nosource/line_number— the evidence is the whole table. (The degenerate one-partition case does locate a line, exactly as in the browser.)samples/bootintel-9.txtprints its table twice: 14 raw regex matches, 7 real partitions. Verified:grep -cfinds 14 matches, 7 unique, and the finding reports7 partitions.detector_labels_stable.Telnet exposure,Autoboot interruptable), which read the original lines — the browser's exemption, now mirrored. The parity test compares every log twice, once behind[12:34:56.123], so this path is actually exercised rather than assumed.The parity test
crates/cli/tests/browser_parity.rs+crates/cli/tests/browser_detectors.mjs: the harness transpilesdetectors.tsin-process and evaluates it in avm(the waycli/test-parity.mjsdoes, resolvingtypescriptfrom the website checkout's ownnode_modules), prints every finding as JSON, and the Rust test diffs the{label, value, detail}sequence againstanalyze()— 31 logs × 2 shapes = 62 comparisons.It skips loudly when
nodeor the website checkout is absent, because the crate is published andcargo testhas to pass for someone who has only this repo.BOOTINTEL_REQUIRE_PARITY=1turns a skip into a failure;BOOTINTEL_BROWSER_DETECTORS=/path/to/detectors.tspoints it at a checkout elsewhere. Run here with the real website checkout present — no skips.Divergences it found (all fixed here)
Each of these made the CLI assert something the web tool did not:
Kerneldetail stitched across lines. The build-metadata group was mandatory and\s-separated, so a banner with no toolchain parentheses matched across line breaks:bootintel-6.txtproducedriscv64-unk2OF: fdt: Ignoring memory range 0x40000000 …,bootintel-17.txtproducedgcc version 4.6.3 (Ti2CPU: ARMv7 Processor [412fc09a] …. Group is now optional and line-bounded. No log loses itsKernelfinding.CPU / Arch: Xtensa (ESP)claimed fromesp32/esp8266. Those name a device family, not a CPU (andDevice familyalready reports it).Init system: BusyBox init. A BusyBox banner says what the userland is, not what PID 1 is.bootintel-17.txtclaimedInit system: BusyBox initand now reportsUserland: BusyBox 1.20.2.Init system: systemdinferred fromWelcome to … Linux. A distro greeting is not evidence of PID 1;systemd[1]:is.toFixedrounds ties away from zero, Rust's{:.1}rounds to even, and partition tables hit exact ties routinely: the 1280 KiBkernelregion inbootintel-12.txtis1.3Min the browser and was1.2Mhere. Tenths are now computed in integer arithmetic.Verified by running
bootintel versionclient-side detectors: 14bootintel detectorscargo test --workspacecargo test --workspace --features tuicargo test --test browser_parity -- --nocapture[skip]linescargo clippy --workspace --all-targets -- -D warnings--features tui)cargo fmt --all --checkcargo deny checkadvisories ok, bans ok, licenses ok, sources okbootintel bench samples/bootintel-24.txt(481 KB)That last row is the cost of the per-line evidence re-run plus five more detectors: ~14% on the largest capture in the corpus, ~1ms for the 40 KiB log the pacer's budget is written around. Worth it for the contract; say the word if you'd rather have a fast path.
Deliberately not done
## [Unreleased]states that the label move is an output-schema change and therefore MAJOR under the policy at the top of the file.samples/. That directory mirrors the 31 public corpus logs, and a synthetic capture in it would desync the mirror and the count assertions. ESP coverage is incrates/detectors/tests/detector_tests.rsinstead (ROM identifier, Firmware SDK, positive + negative each).captureProblem()is still not ported. The browser returns zero findings for a log with a corrupt or conflicting version banner; this crate has no equivalent, so the two could still disagree on such a log. No corpus log trips it, so the parity test passes, but it is a real remaining gap — separate change, since it adds a refusal path with its own exit-code question.🤖 Generated with Claude Code