From d0c22e2b2d45de496c806d4d4dc2663ae8fdde01 Mon Sep 17 00:00:00 2001 From: Albert Hui Date: Mon, 3 Aug 2026 00:47:55 +0800 Subject: [PATCH 1/3] fix(core): take forensicnomicon-core from the registry, not a sibling repo `core/Cargo.toml` declared `forensicnomicon-core = { version = "1.4", path = "../../../knowledge/forensicnomicon/crates/core" }`. That path leaves this repository and lands in the sibling `components/knowledge/forensicnomicon`. CI clones peripheral-forensic alone, so the directory is absent and manifest loading fails before any check begins: error: failed to load manifest for workspace member /home/runner/work/peripheral-forensic/peripheral-forensic/core failed to load manifest for dependency `forensicnomicon-core` No such file or directory (os error 2) That single failure is why 11 checks are red at once -- Clippy, Format, Test, MSRV, Docs, Coverage, cargo-deny, Cargo Vet, the fuzz smoke and both release-plz jobs. None of them is a lint, test, advisory or audit failure; none of them ran. It is invisible locally because the whole fleet is checked out side by side and the path resolves. forensicnomicon-core 1.5.0 is published and satisfies the existing `1.4` caret, so dropping `path` needs no version change: Cargo.lock now records `source = "registry+https://github.com/rust-lang/crates.io-index"`. Verified: `cargo check --workspace --all-targets` compiles, `cargo fmt --all -- --check` and `cargo clippy --workspace --all-targets -- -D warnings` are clean, `cargo test --workspace` passes 75 tests with 0 failures. --- Cargo.lock | 14 +++++--------- core/Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c310ecb..385d77f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -84,19 +84,15 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8340c0ac2fd81daf453f19474b30c75d3b1152c5850c31b3519e20412a7f92d7" dependencies = [ - "forensicnomicon-core 1.3.0", + "forensicnomicon-core", "forensicnomicon-data", ] [[package]] name = "forensicnomicon-core" -version = "1.3.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26131e0c8a646fba012fc958151d480b3d17ddac27f3da2fa37e26a9fdde1e63" - -[[package]] -name = "forensicnomicon-core" -version = "1.4.0" +checksum = "21800c5e29085274fbb324be30790894481c9c5faf8c659b3d0d994840ca0941" [[package]] name = "forensicnomicon-data" @@ -104,7 +100,7 @@ version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e1239a47f728cf7a88d7508f21a446960ae2e7eae41ad14a6bf7caa61548fcf" dependencies = [ - "forensicnomicon-core 1.3.0", + "forensicnomicon-core", ] [[package]] @@ -196,7 +192,7 @@ dependencies = [ name = "peripheral-core" version = "0.8.1" dependencies = [ - "forensicnomicon-core 1.4.0", + "forensicnomicon-core", "shellitem", "winreg-core", ] diff --git a/core/Cargo.toml b/core/Cargo.toml index 4acaf14..809d27c 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -25,7 +25,7 @@ shellitem = "0.2" # Common USB VID->vendor facts are KNOWLEDGE (forensicnomicon), not reader data. # Path dep during the coordinated in-flight change; switch to the registry version # once forensicnomicon publishes the usb_vendors release (bottom-up, ADR-0006). -forensicnomicon-core = { version = "1.4", path = "../../../knowledge/forensicnomicon/crates/core" } +forensicnomicon-core = { version = "1.4" } [lints] workspace = true From 66af121d770bc1b6010fb356183625c3b7798a8a Mon Sep 17 00:00:00 2001 From: Albert Hui Date: Mon, 3 Aug 2026 00:56:35 +0800 Subject: [PATCH 2/3] chore(supply-chain): trust forensicnomicon-core and shellitem from h4x0r Cargo Vet could not resolve the graph while `cargo metadata` was broken, so it reported failure without checking a crate. Now that it runs it names two unvetted dependencies. forensicnomicon-core is mine: it was first-party while it was a path dependency, and taking it from the registry creates a real vetting obligation. shellitem was pre-existing and merely invisible. Both are ours, published to crates.io by h4x0r, so both take ADR-0018 mechanism 2 -- `cargo vet trust`, not an `[[exemptions]]` entry. A trust record binds to the publisher identity rather than a version, so it does not go stale on the next release. `cargo vet --locked` -- CI's exact invocation, which forbids refreshing the publisher cache -- now succeeds. --- supply-chain/audits.toml | 12 ++++++++++++ supply-chain/config.toml | 4 ---- supply-chain/imports.lock | 14 ++++++++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index 2772ccb..a581366 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -2,3 +2,15 @@ # cargo-vet audits file [audits] + +[[trusted.forensicnomicon-core]] +criteria = "safe-to-deploy" +user-id = 347968 # Albert Hui (h4x0r) +start = "2026-06-28" +end = "2027-08-02" + +[[trusted.shellitem]] +criteria = "safe-to-deploy" +user-id = 347968 # Albert Hui (h4x0r) +start = "2026-06-13" +end = "2027-08-02" diff --git a/supply-chain/config.toml b/supply-chain/config.toml index 59e6adc..01dcc24 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -54,10 +54,6 @@ criteria = "safe-to-deploy" version = "1.8.0" criteria = "safe-to-deploy" -[[exemptions.forensicnomicon-core]] -version = "1.3.0" -criteria = "safe-to-deploy" - [[exemptions.forensicnomicon-data]] version = "1.3.1" criteria = "safe-to-deploy" diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index 0da619f..a0d7419 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -1,6 +1,20 @@ # cargo-vet imports lock +[[publisher.forensicnomicon-core]] +version = "1.5.0" +when = "2026-07-29" +user-id = 347968 +user-login = "h4x0r" +user-name = "Albert Hui" + +[[publisher.shellitem]] +version = "0.2.1" +when = "2026-07-25" +user-id = 347968 +user-login = "h4x0r" +user-name = "Albert Hui" + [[publisher.unicode-width]] version = "0.1.14" when = "2024-09-19" From 3eae0cd78f24b918d1e361a5018d6c169228e7d3 Mon Sep 17 00:00:00 2001 From: Albert Hui Date: Mon, 3 Aug 2026 01:02:01 +0800 Subject: [PATCH 3/3] test(core): cover the lines the coverage gate named The 100% line gate could not run while `cargo metadata` was broken. With it running again it names ten uncovered production lines, none annotated `// cov:unreachable`: `DeviceConnection::vendor_name` and `product_name` in core/src/lib.rs, and two branches in core/src/usb_ids.rs -- the `continue` that skips a two-tab interface line, and `parse_id_line`'s rejection when the id is not followed by two spaces. Tests only; no production code changes. Both files now report 100% line coverage locally. Writing the separator test turned up a small deviation worth recording rather than quietly fixing. `parse_id_line`'s doc says the id must be followed by "exactly two spaces", but the check reads positions 4..6 only, so a third space is accepted and survives as a leading space in the name -- `trim_end` does not touch the front. No real usb.ids line reaches it, since the format uses exactly two spaces throughout. `a_wider_separator_is_accepted_and_keeps_ the_surplus_in_the_name` asserts the current behaviour so that changing it is a visible decision instead of a silent one. --- core/src/lib.rs | 60 +++++++++++++++++++++++++++++++++++++++++++++ core/src/usb_ids.rs | 34 +++++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/core/src/lib.rs b/core/src/lib.rs index d8d9707..838f8bb 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -340,4 +340,64 @@ mod tests { ); assert_eq!(Stamp::inferred(10).confidence, Confidence::Inferred); } + + /// A connection carrying only the ids the name lookups read. Every other + /// field is empty on purpose: these two methods must not depend on them. + fn connection(vid: Option, pid: Option) -> DeviceConnection { + DeviceConnection { + bus: Bus::Usb, + device_class_guid: None, + vid, + pid, + device_serial: None, + serial_is_os_generated: false, + friendly_name: None, + device_instance_id: String::new(), + first_install: None, + last_install: None, + last_arrival: None, + last_removal: None, + parent_id_prefix: None, + volume_guid: None, + drive_letter: None, + volume_serial: None, + disk_signature: None, + dma_capable: false, + mitre: Vec::new(), + source: Provenance { + file: String::new(), + line: 0, + key_path: None, + }, + } + } + + const IDS: &str = "0781 SanDisk Corp.\n\t5583 Ultra Fit\n"; + + #[test] + fn vendor_name_resolves_a_known_vid_and_stays_none_otherwise() { + let db = crate::usb_ids::UsbIdDb::parse(IDS); + assert_eq!( + connection(Some(0x0781), None).vendor_name(&db), + Some("SanDisk Corp.") + ); + // Unknown vid: the lookup misses rather than inventing a name. + assert_eq!(connection(Some(0xFFFF), None).vendor_name(&db), None); + // Absent vid: nothing to look up. + assert_eq!(connection(None, None).vendor_name(&db), None); + } + + #[test] + fn product_name_needs_both_ids() { + let db = crate::usb_ids::UsbIdDb::parse(IDS); + assert_eq!( + connection(Some(0x0781), Some(0x5583)).product_name(&db), + Some("Ultra Fit") + ); + // Each half alone falls to the `_ => None` arm: a pid without its vid is + // not a product key, and a vid alone does not name a product. + assert_eq!(connection(Some(0x0781), None).product_name(&db), None); + assert_eq!(connection(None, Some(0x5583)).product_name(&db), None); + assert_eq!(connection(None, None).product_name(&db), None); + } } diff --git a/core/src/usb_ids.rs b/core/src/usb_ids.rs index fc989f2..cbcd219 100644 --- a/core/src/usb_ids.rs +++ b/core/src/usb_ids.rs @@ -169,4 +169,38 @@ C 00 (Defined at Interface level)\n\ assert_eq!(db.vendor_name(0x0781), Some("SanDisk Corp.")); assert!(db.vendor_count() >= 20); } + + #[test] + fn interface_lines_are_skipped_not_read_as_products() { + // Two leading tabs is an interface line inside a product. Reading it as + // a product would register 0x0781:0x0000 "Mass Storage" — a device that + // does not exist. Real usb.ids nests these under most storage devices. + let db = UsbIdDb::parse("0781 SanDisk Corp.\n\t5583 Ultra Fit\n\t\t00 Mass Storage\n"); + assert_eq!(db.product_name(0x0781, 0x5583), Some("Ultra Fit")); + assert_eq!(db.product_name(0x0781, 0x0000), None); + } + + #[test] + fn four_hex_chars_alone_do_not_make_an_id_line() { + // Both lines open with four valid hex characters and are still rejected, + // so they exercise the separator check rather than the hex parse — the + // section headers in FIXTURE (`C 00`) fail earlier, at the hex step. + let db = UsbIdDb::parse("0781 SanDisk Corp.\n0951\tKingston\n"); + assert_eq!(db.vendor_count(), 0); + assert!(db.is_empty()); + assert_eq!(db.vendor_name(0x0781), None); + } + + #[test] + fn a_wider_separator_is_accepted_and_keeps_the_surplus_in_the_name() { + // Current behaviour, asserted so a change is visible rather than silent. + // `parse_id_line`'s doc says "exactly two spaces", but the check reads + // only positions 4..6, so a third space falls into the name and survives + // — `trim_end` does not touch a leading one. usb.ids uses exactly two + // spaces throughout, so no real line reaches this; if the name is ever + // trimmed at the front, this assertion is where it shows up. + let db = UsbIdDb::parse("0abc Three Spaces\n"); + assert_eq!(db.vendor_count(), 1); + assert_eq!(db.vendor_name(0x0abc), Some(" Three Spaces")); + } }