diff --git a/README.md b/README.md
index bf24d57..50d697d 100644
--- a/README.md
+++ b/README.md
@@ -44,8 +44,8 @@ mithril -j
# JSON, for tools and agents
mithril --secrets # secrets only
mithril --sbom -C out/ # SBOM only -> out/sbom.cdx.json + out/sbom.spdx.json
mithril --cve # match components against the local mirror (offline)
-mithril --cve --kernel-cves-all # also list every kernel.org CVE for the kernel version (opt-in)
-mithril --cve --component-cves-all # human view: list every component CVE, not just high-signal (opt-in)
+mithril --cve --kernel-cves-full # also list every kernel.org CVE for the kernel version (opt-in)
+mithril --cve --component-cves-full # human view: list every component CVE, not just high-signal (opt-in)
mithril --licenses # licenses only
mithril --license-paths # licenses, with each license's file locations listed
mithril --boot # boot-security intel: U-Boot env, device tree, FIT/AVB, UEFI Secure Boot
@@ -69,15 +69,15 @@ Naming any of `--secrets` / `--sbom` / `--cve` / `--licenses` / `--boot` / `--ke
- **`mithril --fetch-db`** downloads 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. Needs `curl`. Point it at a mirror with `$MITHRIL_DB_URL`.
- **`mithril --update-db`** rebuilds the index from the upstream feeds yourself. Slower, needs `curl` and `unzip`, and is the authoritative path if you want to control exactly what goes in. Set `$NVD_API_KEY` to raise the NVD rate limit (optional; it works without one, just slower).
-Add **`--with-kernel-feed`** to either command to also get the optional full **kernel.org (Linux CNA) CVE feed** that backs [`--kernel-cves-all`](docs/cve-join.md). By default the kernel is triaged by a curated high-signal checklist; `--kernel-cves-all` lists every kernel.org CVE for the detected version instead, matched branch-aware (a bug fixed in 6.6.17 is reported for 6.6.10 but not 6.6.110). It is a separate asset so the base mirror stays lean; building it needs `tar`.
+Add **`--with-kernel-feed`** to either command to also get the optional full **kernel.org (Linux CNA) CVE feed** that backs [`--kernel-cves-full`](docs/cve-join.md). By default the kernel is triaged by a curated high-signal checklist; `--kernel-cves-full` lists every kernel.org CVE for the detected version instead, matched branch-aware (a bug fixed in 6.6.17 is reported for 6.6.10 but not 6.6.110). It is a separate asset so the base mirror stays lean; building it needs `tar`.
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/shadow` and `htpasswd` hashes 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. The default human view shows only foothold-worthy component CVEs (on CISA KEV, or Critical, or a hard High/Critical floor of CVSS >= 7.0 with EPSS traction, low attack complexity, and real impact or a trending remote DoS), sorted worst-first; it drops Mediums, local DoS, and the high-complexity crypto class. `--component-cves-all` lists them all. JSON always carries the complete set plus a `component_cve_scan` summary.
+- **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). The JSON report always carries the components inline; the standard-format bills of materials (CycloneDX and SPDX) are written as `sbom.cdx.json` and `sbom.spdx.json` when you pass `-C/--outdir`.
+- **cve**: the SBOM joined against the local OSV + NVD/CPE mirror, plus the curated kernel-CVE checklist, annotated with KEV and EPSS. The default human view shows only foothold-worthy component CVEs (on CISA KEV, or Critical, or a hard High/Critical floor of CVSS >= 7.0 with EPSS traction, low attack complexity, and real impact or a trending remote DoS), sorted worst-first; it drops Mediums, local DoS, and the high-complexity crypto class. `--component-cves-full` lists them all. JSON always carries the complete set plus a `component_cve_scan` summary.
- **licenses**: SPDX identification from `SPDX-License-Identifier` tags and LICENSE/COPYING/NOTICE text. Human output summarizes by id and count; `--license-paths` lists each license's file locations, and JSON always carries the full `paths` array.
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`.
diff --git a/VERSION b/VERSION
index 0d91a54..1d0ba9e 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.3.0
+0.4.0
diff --git a/debian/changelog b/debian/changelog
index f645f26..294d0f6 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,20 @@
+mithril (0.4.0-1) unstable; urgency=medium
+
+ * --keys: recover near-zero EC private scalars by a bounded d*G==Q search,
+ detecting the October 2026 eMudhra P-384 CA compromise (P-256/384/521 and
+ secp256k1); the eight real eMudhra intermediates are fingerprinted.
+ * --cve: on an interactive terminal with no local mirror, offer to download
+ it before scanning. New --no-fetch (and MITHRIL_NO_FETCH) suppress the
+ offer; non-interactive runs still error as before.
+ * CLI: breaking flag renames for coherence. --component-cves-all is now
+ --component-cves-full and --kernel-cves-all is now --kernel-cves-full
+ ("all" means all passes; "full" means all detail within a pass). New --full
+ for maximum human detail. --help is regrouped; JSON is always complete.
+ * SBOM: recover components from monolithic non-ELF firmware. Document that
+ -C/--outdir also writes sbom.cdx.json and sbom.spdx.json.
+
+ -- Matt Brown Thu, 22 Oct 2026 15:09:46 -0400
+
mithril (0.3.0-1) unstable; urgency=medium
* New --boot Boot Security pass: U-Boot env, device tree, and FIT posture.
diff --git a/docs/cve-join.md b/docs/cve-join.md
index 9ee4cf1..0035386 100644
--- a/docs/cve-join.md
+++ b/docs/cve-join.md
@@ -131,11 +131,11 @@ a `kernel_cve_scan` object (`method`, `exhaustive: false`, and the `curated_tota
`applicable` / `undetermined` / `ruled_out` counts) so a tool caller never infers exhaustiveness from an
empty array. Second, the curated ranges reach only as far as the entries have been maintained (currently up
to the ~6.8 fix window), so a newer kernel legitimately matches nothing here. For a complete,
-version-matched enumeration use `--kernel-cves-all` (below).
+version-matched enumeration use `--kernel-cves-full` (below).
-## The full kernel CVE feed (`--kernel-cves-all`, opt-in)
+## The full kernel CVE feed (`--kernel-cves-full`, opt-in)
-The curated table is high-signal but partial. `--kernel-cves-all` adds the complete set: every Linux-kernel
+The curated table is high-signal but partial. `--kernel-cves-full` adds the complete set: every Linux-kernel
CVE published by the **kernel.org CNA**, joined against the detected kernel version with branch-aware
precision. It is off by default (it is large and noisy — hundreds of matches on a mature branch — and most
operators want the curated triage set first) and it implies `--cve`.
@@ -168,7 +168,7 @@ LPEs) regardless of assigner.
**Distribution.** The feed is a **separate, optional asset**, so the base mirror stays lean. Build it with
`--update-db --with-kernel-feed` (needs `curl` + `tar`; downloads the vulns snapshot) or fetch the prebuilt
-`kernel-cve-index.json` with `--fetch-db --with-kernel-feed`. Absent, `--kernel-cves-all` fails loud with a
+`kernel-cve-index.json` with `--fetch-db --with-kernel-feed`. Absent, `--kernel-cves-full` fails loud with a
scan-level error naming the fetch command; the curated verdicts still print.
## Exploit annotations: KEV + EPSS (value-add only, never gates)
@@ -185,7 +185,7 @@ IT and misses kernel LPEs, so it must not be a gate. The human view tags a row `
The default human output is meant for a manual pentester triaging by impact, not a compliance dump. A
firmware image pins old C libraries, so a flat join returns dozens to hundreds of component CVEs of every
severity (busybox alone brought 20 on one sample) with no ranking. The default human view therefore shows
-only **high-signal** component CVEs and sorts them worst-first; `--component-cves-all` prints the full
+only **high-signal** component CVEs and sorts them worst-first; `--component-cves-full` prints the full
list.
A component CVE is high-signal if:
@@ -247,6 +247,6 @@ matches stay unclassified → kept). Deferred until the vector heuristic proves
nothing is dropped — and a `component_cve_scan` object (`total` / `high_signal` / `hidden` / `gate`) makes
the human triage set machine-readable. The hard floor is deliberately blunt: a manual pentester triages
Highs and Criticals, and anything below 7.0 (Terrapin CVE-2023-48795 at 5.9 included) goes to
-`--component-cves-all` or the JSON, not the default view. KEV is the one override that ignores the floor,
+`--component-cves-full` or the JSON, not the default view. KEV is the one override that ignores the floor,
so an actively-exploited Medium still shows. Kernel CVEs are gated separately (curated vs.
-`--kernel-cves-all` feed) and are unaffected by this component gate.
+`--kernel-cves-full` feed) and are unaffected by this component gate.
diff --git a/mithril.1 b/mithril.1
index 7dd16d3..e22860f 100644
--- a/mithril.1
+++ b/mithril.1
@@ -1,4 +1,4 @@
-.TH MITHRIL 1 "2026-10-05" "mithril 0.3.0" "User Commands"
+.TH MITHRIL 1 "2026-10-22" "mithril 0.4.0" "User Commands"
.SH NAME
mithril \- analyze firmware contents for secrets, SBOM, CVEs, and licenses
.SH SYNOPSIS
@@ -90,26 +90,35 @@ Run every pass. This is the default when no pass is named.
.TP
.BR \-j ", " \-\-json
Emit a single JSON object instead of the human-readable view. JSON always
-carries the complete result set. Use this when a tool or model will parse the
-output.
+carries the complete result set, so the detail options below only affect the
+human view. Use this when a tool or model will parse the output.
+.TP
+.BR \-v ", " \-\-verbose
+Human view: show full values instead of tail-truncating long ones with
+.RB \(lq ... \(rq.
.TP
.B \-\-license\-paths
List the file locations of each license, not just a count (human output).
Implies
.BR \-\-licenses .
.TP
-.B \-\-component\-cves\-all
+.B \-\-component\-cves\-full
Human view: list every component CVE, not just the high-signal set (on CISA
KEV, or CVSS 9.0 and above, or EPSS 0.10 and above). Implies
.BR \-\-cve .
.TP
-.B \-\-kernel\-cves\-all
+.B \-\-kernel\-cves\-full
List every CVE for the detected kernel version, matched branch-aware, instead
of the curated high-signal checklist. Needs the kernel feed (see
.BR \-\-with\-kernel\-feed ).
Implies
.BR \-\-cve .
.TP
+.B \-\-full
+Maximum human detail in one switch: equivalent to
+.BR \-\-verbose ", " \-\-license\-paths ", " \-\-component\-cves\-full ", and " \-\-kernel\-cves\-full
+together. No effect on JSON, which is always complete.
+.TP
.B \-\-dump\-kconfig
Print the recovered kernel
.I .config
@@ -138,7 +147,7 @@ Given with
or
.BR \-\-update\-db ,
also fetch the optional full kernel.org (Linux CNA) CVE feed that backs
-.BR \-\-kernel\-cves\-all .
+.BR \-\-kernel\-cves\-full .
.TP
.B \-\-no\-fetch
Suppress the interactive offer to download the CVE mirror. When the
@@ -157,8 +166,23 @@ nonzero, as in a non-interactive run. Use it in scripts and CI.
Add user-defined detection rules from a JSON file.
.TP
.BR \-C ", " \-\-outdir " " \fIDIR\fR
-Write the JSON report to
-.IR DIR /mithril\-report.json .
+Also write the report to
+.IR DIR ,
+regardless of the stdout format:
+.I mithril\-report.json
+(the complete JSON result set) and, when the SBOM pass runs,
+.I sbom.cdx.json
+and
+.I sbom.spdx.json
+(the CycloneDX and SPDX bills of materials). These standard-format SBOMs are
+produced only through
+.BR \-C .
+Note that
+.BR moria (1)
+also has a
+.B \-C
+but means its extraction output directory; here it is the report output
+directory.
.TP
.BI \-\-threads " N"
Worker threads for tree scans. Default: automatic.
diff --git a/src/cve.cpp b/src/cve.cpp
index 7e7a790..6559a12 100644
--- a/src/cve.cpp
+++ b/src/cve.cpp
@@ -236,7 +236,7 @@ bool cve_is_high_signal(const CveMatch& m) {
// A vendor-fork component matched only by an unbounded-below range: we cannot
// confirm the fork's base carries the vulnerable code (e.g. a 0.8.x_rtw hostapd
// predates SAE), so drop it from the default view. Still listed under
- // --component-cves-all with the "verify" basis. KEV above overrides this.
+ // --component-cves-full with the "verify" basis. KEV above overrides this.
if (m.fork && m.unbounded_below) return false;
double score = cvss_base_score(m.severity);
if (score >= kHighSignalCvss) return true; // Critical, any shape
diff --git a/src/cve.hpp b/src/cve.hpp
index 635ecf0..2a7bc36 100644
--- a/src/cve.hpp
+++ b/src/cve.hpp
@@ -106,7 +106,7 @@ double cvss_base_score(const std::string& vector);
// - it is on the CISA KEV catalog (exploited in the wild) -- unconditional; OR
// - (it is NOT a vendor-fork component matched only by an unbounded-below range:
// such a match cannot be confirmed to apply -- a fork's base may predate the
-// vulnerable code -- so it is demoted to the --component-cves-all list); AND
+// vulnerable code -- so it is demoted to the --component-cves-full list); AND
// - its CVSS base score is >= 9.0 (Critical) -- any shape; OR
// - it clears a hard High/Critical floor (base score >= 7.0) AND has EPSS
// traction (>= 0.10) AND is low-complexity (AC:L) AND EITHER
@@ -118,7 +118,7 @@ double cvss_base_score(const std::string& vector);
// but low-yield crypto/DoS CVEs old libraries accumulate (SWEET32, DROWN, padding
// oracles). A pure DoS cannot reach CVSS 9.0, so the Critical arm needs no test.
// Scores are computed for CVSS v2 and v3 alike so the floor judges old CVEs
-// instead of hiding them. --component-cves-all bypasses the whole gate; kernel
+// instead of hiding them. --component-cves-full bypasses the whole gate; kernel
// CVEs gate separately.
constexpr double kHighSignalCvss = 9.0; // Critical: always shown
constexpr double kHighFloorCvss = 7.0; // hard floor: High/Critical only below Critical
diff --git a/src/cveupdate.cpp b/src/cveupdate.cpp
index 42fbb4a..069a8d1 100644
--- a/src/cveupdate.cpp
+++ b/src/cveupdate.cpp
@@ -537,7 +537,7 @@ int epss_update(std::string& err) {
}
// Build the full kernel.org (Linux CNA) CVE feed index from the vulns snapshot.
-// A large, optional asset (only --kernel-cves-all reads it): fetch the cgit
+// A large, optional asset (only --kernel-cves-full reads it): fetch the cgit
// tarball, extract with tar, reconstruct one entry per published CVE record,
// write kernel-cve-index.json. Needs curl + tar.
int kernel_feed_update(std::string& err) {
diff --git a/src/cveupdate.hpp b/src/cveupdate.hpp
index a19de48..3a9916e 100644
--- a/src/cveupdate.hpp
+++ b/src/cveupdate.hpp
@@ -15,7 +15,7 @@ namespace ft {
// success; on failure returns non-zero and sets `err`. Progress to stderr.
// This is the authoritative rebuild path (scrapes OSV + NVD at the source).
// `with_kernel_feed` additionally builds the full kernel.org CVE feed (a large
-// extra download; off by default — it only serves --kernel-cves-all).
+// extra download; off by default — it only serves --kernel-cves-full).
int cve_update(std::string& err, bool with_kernel_feed = false);
// Download a prebuilt index from the rolling release (or $MITHRIL_DB_URL) and
diff --git a/src/dbpath.hpp b/src/dbpath.hpp
index fd8d2eb..e103a67 100644
--- a/src/dbpath.hpp
+++ b/src/dbpath.hpp
@@ -24,7 +24,7 @@ std::string kev_index_path();
std::string epss_index_path();
// The full kernel.org (Linux CNA) CVE feed index, consulted only by
-// --kernel-cves-all. An optional asset: fetched/built out-of-band, absent by
+// --kernel-cves-full. An optional asset: fetched/built out-of-band, absent by
// default so the base mirror stays lean.
std::string kernel_feed_path();
diff --git a/src/human.cpp b/src/human.cpp
index 7fb7c21..75b9b74 100644
--- a/src/human.cpp
+++ b/src/human.cpp
@@ -165,7 +165,7 @@ std::string emit_report_human(const Report& rep, const Passes& passes, const std
// ---- CVE matches ----
// The default view is gated to high-signal component CVEs (KEV / CVSS>=9.0 /
// EPSS>=0.10) and sorted worst-first, so a manual pentester triages by impact
- // instead of reading a flat dump of every range match. --component-cves-all
+ // instead of reading a flat dump of every range match. --component-cves-full
// shows the full list; JSON (-j) is always complete regardless of the gate.
if (passes.cve) {
if (passes.secrets || passes.sbom) o += "\n";
@@ -194,7 +194,7 @@ std::string emit_report_human(const Report& rep, const Passes& passes, const std
}
o += "\n";
- // Rows to render: high-signal only (default) or all (--component-cves-all).
+ // Rows to render: high-signal only (default) or all (--component-cves-full).
std::vector show;
for (const auto& r : rows)
if (rep.component_cves_all || r.high) show.push_back(r);
@@ -259,7 +259,7 @@ std::string emit_report_human(const Report& rep, const Passes& passes, const std
o += a.dim();
o += "\n Gated to high-signal (KEV, or High/Critical CVSS>=7.0 with EPSS "
"traction, real impact or trending remote DoS).\n"
- " Run --component-cves-all for every component CVE in range.\n";
+ " Run --component-cves-full for every component CVE in range.\n";
o += a.reset();
}
}
@@ -404,7 +404,7 @@ std::string emit_report_human(const Report& rep, const Passes& passes, const std
o += "\n";
}
}
- // --kernel-cves-all: the full kernel.org feed, version-matched. Can be
+ // --kernel-cves-full: the full kernel.org feed, version-matched. Can be
// hundreds of lines, so cap the human list and point at -j for the rest.
if (rep.kernel_cves_full) {
o += "\n";
@@ -459,7 +459,7 @@ std::string emit_report_human(const Report& rep, const Passes& passes, const std
o += rep.kernel_cves_full
? " Curated entries are kconfig-gated; feed entries are version-matched "
"(confirm the backport)."
- : " Curated high-signal set. Run --kernel-cves-all for every kernel CVE in range.";
+ : " Curated high-signal set. Run --kernel-cves-full for every kernel CVE in range.";
o += a.reset();
o += "\n";
}
diff --git a/src/json.cpp b/src/json.cpp
index 934ab65..63d9f40 100644
--- a/src/json.cpp
+++ b/src/json.cpp
@@ -227,7 +227,7 @@ std::string emit_report_json(const Report& rep, const Passes& passes) {
"(real-C/I-impact OR remote-DoS with epss>=0.70))", sf);
kv_str(o, "note",
"the \"cves\" array is complete; \"high_signal\" is the count shown in "
- "the default human view; run --component-cves-all to list all in human output",
+ "the default human view; run --component-cves-full to list all in human output",
sf);
o += "}";
}
@@ -293,7 +293,7 @@ std::string emit_report_json(const Report& rep, const Passes& passes) {
? "curated entries are kconfig-gated; feed entries (source "
"\"kernel.org\") are version-matched, confirm the backport"
: "curated high-signal set of widely-exploited kernel bugs, not every "
- "CVE affecting this version; run --kernel-cves-all for the full feed",
+ "CVE affecting this version; run --kernel-cves-full for the full feed",
sf);
o += "}";
}
diff --git a/src/kernelcve.hpp b/src/kernelcve.hpp
index 4e422a0..4b032f1 100644
--- a/src/kernelcve.hpp
+++ b/src/kernelcve.hpp
@@ -36,7 +36,7 @@ struct KernelCveResult {
bool applicable = true; // == (state == Applicable); kept for existing callers
std::string reason; // why applicable/ruled-out/unknown, with the evidence source
// Where this result came from: "curated" (the built-in high-signal table) or
- // "kernel.org" (the full version-matched feed, only with --kernel-cves-all).
+ // "kernel.org" (the full version-matched feed, only with --kernel-cves-full).
std::string source = "curated";
// Value-add annotations (never affect applicability):
bool kev = false; // on the CISA Known-Exploited catalog
diff --git a/src/kernelfeed.hpp b/src/kernelfeed.hpp
index dfdbf35..3a96c01 100644
--- a/src/kernelfeed.hpp
+++ b/src/kernelfeed.hpp
@@ -1,4 +1,4 @@
-// kernelfeed.hpp — the full kernel.org (Linux CNA) CVE feed, for --kernel-cves-all.
+// kernelfeed.hpp — the full kernel.org (Linux CNA) CVE feed, for --kernel-cves-full.
//
// The curated table (kernelcve.hpp) is the high-signal default: a hand-picked set
// of widely-exploited kernel bugs, kconfig-gated. It is deliberately partial and
diff --git a/src/main.cpp b/src/main.cpp
index 19e2e23..08e55c4 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -46,30 +46,38 @@ void print_help(std::FILE* out, const char* prog, bool color) {
std::fprintf(out, "Analyze software contents: secrets, components (SBOM), CVEs, licenses.\n\n");
std::fprintf(out, "%sUSAGE%s\n", B, R);
std::fprintf(out, " %s [options] \n\n", prog);
- std::fprintf(out, "%sOPTIONS%s\n", B, R);
+ std::fprintf(out, "%sPASSES%s (with none named, every pass runs)\n", B, R);
std::fprintf(out,
- " -j, --json JSON output (default: human-readable)\n"
- " -v, --verbose Human output: show full values (no \"...\" truncation)\n"
" --secrets Scan file content for credential/key material\n"
" --sbom Inventory software components and versions\n"
" --cve Match components against known vulnerabilities\n"
" --licenses Identify open-source licenses\n"
" --boot Boot-security intel: U-Boot env, device tree, FIT\n"
" --keys Public-key weakness: leaked/default keys, ROCA, weak RSA\n"
- " --license-paths List the file locations of each license, not just\n"
- " a count (human output; implies --licenses)\n"
- " -A, --all Run every pass (the default when none is selected)\n"
- " --kernel-cves-all List every CVE for the detected kernel version, not\n"
- " just the curated set (needs the kernel feed; implies --cve)\n"
- " --component-cves-all Human view: list every component CVE, not just the\n"
- " high-signal set (KEV / CVSS>=9.0 / EPSS>=0.10; implies --cve)\n"
- " --rules Add user-defined rules from a JSON file\n"
+ " -A, --all Run every pass (the explicit form of the default)\n\n");
+ std::fprintf(out, "%sOUTPUT%s\n", B, R);
+ std::fprintf(out,
+ " -j, --json JSON instead of human-readable. JSON is ALWAYS\n"
+ " complete; the flags below only expand the human view.\n"
+ " -v, --verbose Show full values, no \"...\" truncation\n"
+ " --license-paths List where each license was found (implies --licenses)\n"
+ " --component-cves-full List every component CVE, not just the\n"
+ " high-signal set (implies --cve)\n"
+ " --kernel-cves-full List every CVE for the detected kernel version, not\n"
+ " just the curated set (needs the kernel feed; implies --cve)\n"
+ " --full Everything above at once (verbose + paths + both -cves-full)\n"
+ " -C, --outdir Also write the report to DIR: mithril-report.json, plus\n"
+ " sbom.cdx.json and sbom.spdx.json when the SBOM pass runs\n\n");
+ std::fprintf(out, "%sCVE MIRROR%s\n", B, R);
+ std::fprintf(out,
" --fetch-db Download a prebuilt CVE mirror, then exit\n"
" --update-db Rebuild the local CVE mirror from source, then exit\n"
" --with-kernel-feed With --fetch-db/--update-db, also get the kernel feed\n"
- " --no-fetch Never offer to download the CVE mirror (for scripts/CI;\n"
- " env: MITHRIL_NO_FETCH). A missing mirror then just errors.\n"
- " -C, --outdir Write the JSON report to DIR/mithril-report.json\n"
+ " --no-fetch Never offer to download the mirror (for scripts/CI;\n"
+ " env: MITHRIL_NO_FETCH). A missing mirror then just errors.\n\n");
+ std::fprintf(out, "%sOTHER%s\n", B, R);
+ std::fprintf(out,
+ " --rules Add user-defined rules from a JSON file\n"
" --dump-kconfig Print the recovered kernel .config to stdout, then exit\n"
" --threads Worker threads for tree scans (default: auto)\n"
" -h, --help Print help\n"
@@ -78,10 +86,8 @@ void print_help(std::FILE* out, const char* prog, bool color) {
std::fprintf(out,
" %s ./rootfs/ Run every analysis on an unpacked tree\n"
" %s --secrets ./rootfs/ Only the secrets pass\n"
- " %s -j ./rootfs/ JSON for tools / LLMs\n\n",
+ " %s -j ./rootfs/ JSON for tools / LLMs\n",
prog, prog, prog);
- std::fprintf(out, "Pipeline: moria -e firmware.bin -> mithril firmware.bin.extracted/\n");
- std::fprintf(out, "Docs: README.md, docs/\n");
}
@@ -150,14 +156,18 @@ int main(int argc, char** argv) {
fetch_db = true;
} else if (!end_of_opts && std::strcmp(a, "--dump-kconfig") == 0) {
dump_kconfig = true;
- } else if (!end_of_opts && std::strcmp(a, "--kernel-cves-all") == 0) {
+ } else if (!end_of_opts && std::strcmp(a, "--kernel-cves-full") == 0) {
kernel_cves_all = true;
- } else if (!end_of_opts && std::strcmp(a, "--component-cves-all") == 0) {
+ } else if (!end_of_opts && std::strcmp(a, "--component-cves-full") == 0) {
component_cves_all = true;
} else if (!end_of_opts && std::strcmp(a, "--license-paths") == 0) {
license_paths = true;
} else if (!end_of_opts && (std::strcmp(a, "-v") == 0 || std::strcmp(a, "--verbose") == 0)) {
verbose = true;
+ } else if (!end_of_opts && std::strcmp(a, "--full") == 0) {
+ // One switch for maximum human detail: every "show more" knob at once.
+ // (JSON is already complete, so --full is a no-op there.)
+ verbose = component_cves_all = kernel_cves_all = license_paths = true;
} else if (!end_of_opts && std::strcmp(a, "--with-kernel-feed") == 0) {
with_kernel_feed = true;
} else if (!end_of_opts && std::strcmp(a, "--no-fetch") == 0) {
@@ -203,7 +213,7 @@ int main(int argc, char** argv) {
return 1;
}
- // --kernel-cves-all / --component-cves-all are CVE-pass features; make sure
+ // --kernel-cves-full / --component-cves-full are CVE-pass features; make sure
// that pass runs.
if (kernel_cves_all) passes.cve = true;
if (component_cves_all) passes.cve = true;
@@ -379,7 +389,7 @@ int main(int argc, char** argv) {
// the OSV/NVD mirror — the table is built in).
rep.kernel_cves = kernel_cve_scan(rep.kernel_version, &rep.kcv);
- // --kernel-cves-all: also join the full kernel.org (Linux CNA) feed, the
+ // --kernel-cves-full: also join the full kernel.org (Linux CNA) feed, the
// complete version-matched set. Merged in, deduped against the curated
// entries (which keep their kconfig gating and exploit notes). Feed entries
// are version-matched only (the feed carries no CONFIG data), so they are
@@ -387,7 +397,7 @@ int main(int argc, char** argv) {
if (kernel_cves_all && !rep.kernel_version.empty()) {
auto feed = load_kernel_feed(kernel_feed_path());
if (!feed) {
- std::string msg = std::string("--kernel-cves-all: no kernel CVE feed; run '") +
+ std::string msg = std::string("--kernel-cves-full: no kernel CVE feed; run '") +
prog + " --fetch-db --with-kernel-feed' first";
rep.errors.emplace_back(std::string(), msg);
std::fprintf(stderr, "%s: %s\n", prog, msg.c_str());
diff --git a/src/report.hpp b/src/report.hpp
index b24cee6..58d318f 100644
--- a/src/report.hpp
+++ b/src/report.hpp
@@ -57,9 +57,9 @@ struct Report {
std::vector notable; // path-rule hits (credential/crypto/config files)
std::vector components; // SBOM components (dpkg/opkg/apk/...)
std::vector cves; // SBOM-vs-OSV join results
- std::vector kernel_cves; // curated checklist (+ feed if --kernel-cves-all)
- bool kernel_cves_full = false; // --kernel-cves-all: the kernel.org feed was merged in
- bool component_cves_all = false; // --component-cves-all: show every component CVE in human view
+ std::vector kernel_cves; // curated checklist (+ feed if --kernel-cves-full)
+ bool kernel_cves_full = false; // --kernel-cves-full: the kernel.org feed was merged in
+ bool component_cves_all = false; // --component-cves-full: show every component CVE in human view
std::vector licenses; // license findings (SPDX tags + license files)
std::vector boot; // boot-security findings (U-Boot env, DTB/FIT)
std::vector keys; // key-weakness findings (leaked/ROCA/weak public keys)
diff --git a/tests/test_cve.py b/tests/test_cve.py
index 5eb057a..06bab2d 100644
--- a/tests/test_cve.py
+++ b/tests/test_cve.py
@@ -157,13 +157,13 @@ def main():
if "CVE-2099-0002" not in h:
print(f"FAIL: real-impact CVE should survive the EPSS arm:\n{h}")
fails += 1
- if "--component-cves-all" not in h:
- print(f"FAIL: gated human view should point to --component-cves-all:\n{h}")
+ if "--component-cves-full" not in h:
+ print(f"FAIL: gated human view should point to --component-cves-full:\n{h}")
fails += 1
- # --component-cves-all lists every CVE, with no gate banner or footer.
- hall = run_human(binary, os.path.join(d, "vuln"), dbdir, ["--component-cves-all"])
+ # --component-cves-full lists every CVE, with no gate banner or footer.
+ hall = run_human(binary, os.path.join(d, "vuln"), dbdir, ["--component-cves-full"])
if "CVE-2021-28831" not in hall or "CVE-2099-0002" not in hall or "hidden" in hall:
- print(f"FAIL: --component-cves-all should list every CVE:\n{hall}")
+ print(f"FAIL: --component-cves-full should list every CVE:\n{hall}")
fails += 1
# No local mirror: a non-interactive --cve run must not prompt (stdin is
@@ -193,7 +193,7 @@ def main():
print("PASS: vulnerable busybox flagged CVE-2021-28831 (severity+basis), "
"patched clean, NVD/CPE join flagged openssl 1.1.1m, "
"human view gates to foothold-worthy (hard High/Critical floor; remote DoS "
- "kept only when trending; --component-cves-all opts out)")
+ "kept only when trending; --component-cves-full opts out)")
return 0
return 1
diff --git a/tests/test_kernelfeed.py b/tests/test_kernelfeed.py
index 75fcd73..c288938 100644
--- a/tests/test_kernelfeed.py
+++ b/tests/test_kernelfeed.py
@@ -1,15 +1,15 @@
#!/usr/bin/env python3
-"""test_kernelfeed.py — integration test for --kernel-cves-all (the full
+"""test_kernelfeed.py — integration test for --kernel-cves-full (the full
kernel.org CVE feed) and its merge with the curated checklist.
Seeds a small kernel-cve-index.json fixture (the on-disk feed format) into a
throwaway MITHRIL_DB, builds a tiny tree carrying a kernel banner, and asserts:
- 1. --kernel-cves-all surfaces the in-range feed CVEs (source "kernel.org"),
+ 1. --kernel-cves-full surfaces the in-range feed CVEs (source "kernel.org"),
branch-aware: a bug fixed in 6.6.17 hits 6.6.10 but NOT 6.6.110, even though
the mainline window spans both.
2. the JSON kernel_cve_scan flips to method "curated+kernel.org", exhaustive
true, with a feed_in_range count.
- 3. without the feed asset, --kernel-cves-all fails loud (a scan-level error
+ 3. without the feed asset, --kernel-cves-full fails loud (a scan-level error
naming --fetch-db --with-kernel-feed), not silently.
Usage: tests/test_kernelfeed.py [path/to/mithril]
@@ -73,7 +73,7 @@ def main():
# --- Case 1: 6.6.10 -> branch-precise bug + coarse bug apply, 5.15 one not ---
with open(os.path.join(tree, "vmlinuz"), "wb") as f:
f.write(banner("6.6.10") + os.urandom(4096))
- rep = run_json(binary, tree, env, extra=["--kernel-cves-all"])
+ rep = run_json(binary, tree, env, extra=["--kernel-cves-full"])
feed = {k["cve"]: k for k in rep.get("kernel_cves", []) if k.get("source") == "kernel.org"}
if "CVE-2024-90001" not in feed:
print(f"FAIL(1): branch-precise CVE not matched for 6.6.10: {sorted(feed)}")
@@ -99,7 +99,7 @@ def main():
# --- Case 2: 6.6.110 -> the 6.6-branch bug is fixed (6.6.17); NOT reported ---
with open(os.path.join(tree, "vmlinuz"), "wb") as f:
f.write(banner("6.6.110") + os.urandom(4096))
- rep2 = run_json(binary, tree, env, extra=["--kernel-cves-all"])
+ rep2 = run_json(binary, tree, env, extra=["--kernel-cves-full"])
feed2 = {k["cve"]: k for k in rep2.get("kernel_cves", []) if k.get("source") == "kernel.org"}
if "CVE-2024-90001" in feed2:
print("FAIL(2): 6.6.110 wrongly flagged for a bug fixed in 6.6.17 (cross-branch FP)")
@@ -108,21 +108,21 @@ def main():
print("FAIL(2): coarse-range CVE should still match 6.6.110")
fails += 1
- # --- Case 3: default run (no --kernel-cves-all) does not consult the feed ---
+ # --- Case 3: default run (no --kernel-cves-full) does not consult the feed ---
rep3 = run_json(binary, tree, env)
if any(k.get("source") == "kernel.org" for k in rep3.get("kernel_cves", [])):
- print("FAIL(3): feed CVEs leaked into a default scan (no --kernel-cves-all)")
+ print("FAIL(3): feed CVEs leaked into a default scan (no --kernel-cves-full)")
fails += 1
if rep3.get("kernel_cve_scan", {}).get("exhaustive") is not False:
print("FAIL(3): default scan should be non-exhaustive")
fails += 1
- # --- Case 4: --kernel-cves-all with no feed asset -> loud, actionable error ---
+ # --- Case 4: --kernel-cves-full with no feed asset -> loud, actionable error ---
with tempfile.TemporaryDirectory() as db2, tempfile.TemporaryDirectory() as tree2:
env2 = {**os.environ, "MITHRIL_DB": db2, "NO_COLOR": "1"}
with open(os.path.join(tree2, "vmlinuz"), "wb") as f:
f.write(banner("6.6.10") + os.urandom(4096))
- r = subprocess.run([binary, "--cve", "--kernel-cves-all", tree2],
+ r = subprocess.run([binary, "--cve", "--kernel-cves-full", tree2],
capture_output=True, env=env2)
msg = (r.stdout + r.stderr).decode()
if "no kernel CVE feed" not in msg or "--with-kernel-feed" not in msg:
@@ -130,7 +130,7 @@ def main():
fails += 1
if fails == 0:
- print("PASS: --kernel-cves-all merges the branch-aware kernel.org feed, "
+ print("PASS: --kernel-cves-full merges the branch-aware kernel.org feed, "
"excludes fixed branches, and fails loud without the asset")
return 0
return 1