IoT static scanner for secrets, SBOM, CVEs and more.
mithril analyzes the contents of firmware and IoT software, whether a single file or an unpacked rootfs. It looks for four things: embedded secrets and keys, a software bill of materials (SBOM), known CVEs affecting those components, and open-source licenses. It reports each finding with its evidence and a confidence, and it speaks clean JSON so scripts and LLM agents can drive it as easily as people can.
A scan makes no network calls. Secrets, SBOM, and license analysis are fully offline, and the CVE pass reads a local vulnerability mirror you refresh out of band.
mithril is the semantic companion to moria, which identifies and unpacks firmware. moria maps the bytes. mithril reads the contents.
moria -e firmware.bin # identify + unpack -> firmware.bin.extracted/
mithril firmware.bin.extracted/ # secrets + SBOM + CVEs + licenses
It has no build-time dependency on moria. Point it at any file or directory.
Why mithril
- Firmware-aware SBOM. Beyond package databases (dpkg/opkg/apk/rpm), it recovers components from ELF version banners, versioned libc filenames, and the kernel banner, so it finds openssl/busybox/uClibc even in a stripped image with no package manager. Emitted as CycloneDX and SPDX.
- CVEs it owns end to end. A local OSV + NVD/CPE mirror plus a curated, version- and kconfig-gated kernel-CVE checklist. Every match records how sure it is (exact version vs affected range vs CPE), stays release-precise where
/etc/os-releaseallows, and is annotated with CISA KEV and EPSS. - Honest by construction. Secrets ride a deterministic offline ladder:
pattern(shape), thenstructural(a parsed JWT or PEM key), thenvalidated(a recomputed checksum, such as GitHub tokens and crypt hashes). mithril asserts well-formed, never "live," and an empty result is a real answer rather than a tool failure. - Fully offline and reproducible. No network at scan time; the CVE mirror is refreshed by an explicit, separate step. Air-gappable.
- Safe on hostile input. Untrusted bytes go through one bounds-checked reader. Parsers degrade to a clean error rather than crash, and the CVE index is memory-mapped so a scan never parses the whole thing.
Build & Install
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
cmake --install build --prefix ~/.local # or /usr/local (needs sudo)
No third-party libraries. The install step places the mithril binary; a bare cp build/mithril ~/.local/bin works just as well. Debug with sanitizers: cmake -S . -B build -DMT_SANITIZE=ON.
Usage
Output is human-readable by default. Pass -j for JSON.
mithril <file|dir> # run every pass (human-readable)
mithril -j <dir> # JSON, for tools and agents
mithril --secrets <dir> # secrets only
mithril --sbom -C out/ <dir> # SBOM only -> out/sbom.cdx.json + out/sbom.spdx.json
mithril --cve <dir> # match components against the local mirror (offline)
mithril --licenses <dir> # licenses only
mithril --rules my.json <dir> # add user-defined rules (docs/user-rules.md)
mithril --fetch-db # FIRST RUN: download the CVE mirror (a networked command)
mithril --update-db # rebuild the CVE mirror from source (a networked command)
mithril --help
First run: the --cve pass needs a local vulnerability mirror, so run mithril --fetch-db once before your first CVE scan (details under The CVE mirror). Without it, --cve reports the missing DB and exits nonzero. Secrets, SBOM, and licenses are fully offline and need no setup.
Naming any of --secrets / --sbom / --cve / --licenses runs just those. Naming none runs all four.
The CVE mirror
--cve reads a local mirror under ~/.local/share/mithril/ (honors $MITHRIL_DB), a compact, memory-mapped index built from OSV.dev, the NVD 2.0 API, the CISA KEV catalog, and FIRST's EPSS scores that a scan loads in a fraction of a second. Get it two ways, and only these two commands ever touch the network:
mithril --fetch-dbdownloads a prebuilt index (rebuilt weekly) and verifies every file against a published SHA-256 before installing. It does no upstream scraping: it just fetches the finished index. Needscurl. Point it at a mirror with$MITHRIL_DB_URL.mithril --update-dbrebuilds the index from the upstream feeds yourself. Slower, needscurlandunzip, and is the authoritative path if you want to control exactly what goes in. Set$NVD_API_KEYto raise the NVD rate limit (optional; it works without one, just slower).
Build it once either way; every scan after that is offline. See docs/cve-join.md and docs/cve-index-format.md.
Scope
- secrets: credential and key material in file content (cloud keys, tokens, JWTs, private keys, high-entropy
KEY=…values)./etc/shadowandhtpasswdhashes are classified with weak-algorithm and empty-password flags, and credential/crypto/config files are flagged by path. - sbom: components and versions from package databases, language manifests, binary version strings, libc filenames, and the kernel banner, keyed on purl (with CPE co-derived), emitted as CycloneDX and SPDX.
- cve: the SBOM joined against the local OSV + NVD/CPE mirror, plus the curated kernel-CVE checklist, annotated with KEV and EPSS.
- licenses: SPDX identification from
SPDX-License-Identifiertags and LICENSE/COPYING/NOTICE text.
File-type identification, extraction, and embedded key/certificate file signatures are moria's job; mithril reads content, it does not unpack. How discovery works is documented in docs/engine-design.md.
Comments