forensic-mount 0.6.2

Mount forensic disk images, archives, and memory dumps as a filesystem on Linux, macOS, and Windows — ext4/NTFS/exFAT/HFS+/APFS/ISO, EWF/VMDK containers, zip/7z/tar, LiME/AVML/crash dumps
Documentation
[package]
name = "forensic-mount"
version = "0.6.2"
edition = "2021"
rust-version = "1.93"
# Required by cargo-deb (it errors without a copyright or authors property).
authors = ["Albert Hui <albert@securityronin.com>"]
description = "Mount forensic disk images, archives, and memory dumps as a filesystem on Linux, macOS, and Windows — ext4/NTFS/exFAT/HFS+/APFS/ISO, EWF/VMDK containers, zip/7z/tar, LiME/AVML/crash dumps"
license = "Apache-2.0"
repository = "https://github.com/SecurityRonin/4n6mount"
homepage = "https://github.com/SecurityRonin/4n6mount"
documentation = "https://docs.rs/forensic-mount"
readme = "README.md"
keywords = ["forensics", "fuse", "dfir", "filesystem", "memory-forensics"]
categories = ["filesystem", "command-line-utilities"]
# Test fixtures, CI smoke harness, and workflows are not needed by consumers.
exclude = ["tests/data/*", "scripts/smoke/*", ".github/*", "fuzz/*"]

[[bin]]
name = "4n6mount"
path = "src/main.rs"

# Synthetic crash-dump generator for the memory mount-smoke; needs the memory
# feature's memf-format builders, so it is skipped under default features.
[[example]]
name = "mkdump"
required-features = ["memory"]

[features]
# The forensic-vfs engine is batteries-included: every filesystem/container
# reader is compiled in, so there are no per-format features to forward. Only
# the memory-dump mount (memf) stays opt-in — it pulls a large analysis stack.
default = []
memory = ["dep:memf-format", "dep:memf-core", "dep:memf-session", "dep:memf-symbols", "dep:memf-windows", "dep:memf-linux", "dep:forensic-vfs-resolver"]

[dependencies]
clap = { version = "4", features = ["derive"] }
# The forensic VFS engine: the partition-aware `Vfs::open()` entry point that
# detects the container/volume/filesystem stack and mounts a `dyn FileSystem`.
# Requires >= 0.1.6: engine 0.1.6 (ADR-0014) surfaces archive (zip/7z/tar) and
# logical (AD1/AFF4-Logical/DAR) containers as browsable `forensic_vfs::FileSystem`s
# through `Vfs::open`/`open_all`, so the MultiPartitionFs path mounts them as one
# `root` volume — no local SyntheticFs, no disk-forensic. (Engine 0.1.4 links
# forensic-vfs 0.4, which does NOT unify with the `forensic-vfs = "0.7"` this crate
# pins — two incompatible `FileSystem` traits -> E0277.) Registry (not a path dep)
# so `cargo package`/release-plz can resolve it in isolation and the whole fleet
# graph unifies on one version.
forensic-vfs-engine = "0.1.7"
# The `FileSystem` navigation contract + its `Fs*` value types. Registry version
# (not a path dep) so it unifies with the copy the engine links — a path dep
# would compile a second, incompatible `DynFs`/`FileId`.
forensic-vfs = "0.7"
# The generic layer resolver's `resolve_to_source()` (ADR 0011) — peels a wrapped
# memory dump (memory.zip / memory.dd.gz / dump.7z) down to its raw byte edge
# before memf reads it. Only the memory mount needs it; already in the graph via
# the engine, so this is a zero-cost direct handle. Registry version so it unifies
# with the engine's copy (both resolve to the published forensic-vfs-resolver 0.3).
forensic-vfs-resolver = { version = "0.3", optional = true }
# Archive-layer reader: transparently peels a gzip/bzip2/xz compression wrapper
# (evidence.dd.gz -> dd) before the engine opens the inner image. The `vfs`
# feature exposes `ArchiveOpener` (the forensic-vfs ArchiveOpen impl) so the
# memory path can build an archive-only `Openers` for resolve_to_source.
archive-core = { version = "0.1.0", features = ["vfs"] }
tempfile = "3"
memf-format = { version = "0.3", optional = true }
memf-core = { version = "0.2", optional = true }
memf-session = { version = "0.3", optional = true }
memf-symbols = { version = "0.2", optional = true }
memf-windows = { version = "0.3", optional = true }
memf-linux = { version = "0.3", optional = true }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sha2 = "0.10"
md-5 = "0.10"
rusqlite = { version = "0.31", features = ["bundled"] }
libc = "0.2"

# FUSE backend is Unix-only (libfuse / macFUSE). On Windows the mount layer uses
# Dokan instead (see the windows target table below).
[dev-dependencies]
# The peel_open integration test gzips a fixture to prove the archive-layer peel.
flate2 = "1"
# The e2e_archive_read test drives the SAME archive reader the mount peels
# evidence with (`archive_core::Archive`) against a real catalog .zip.
archive-core = { version = "0.1.0" }
# The ADR-0011 memory-unarchive test wraps a synthetic dump in a real .zip to
# prove the resolve_to_source -> adapt -> open_source chain reads the same pages.
zip = { version = "2", default-features = false, features = ["deflate"] }

[target.'cfg(unix)'.dependencies]
# 0.16.0 is the RUSTSEC-2021-0154 patched floor: `Session::new` passed a NULL
# operation list to libfuse's `fuse_session_new`, which expects a real list, so
# libfuse read uninitialized memory and leaked. 0.17 rewrote the `Filesystem`
# trait (INodeNo/FileHandle/OpenFlags newtypes, `&self` receivers) and is held
# for a dedicated migration rather than folded into a security patch.
#
# `libfuse` must be named explicitly: 0.16.0 changed `default = ["libfuse"]` to
# `default = []`, and without it the build script refuses to build on anything
# but Linux ("Building without libfuse is only supported on Linux"). Requesting
# it restores exactly the 0.15 feature set on every platform — macFUSE via
# libfuse2 on macOS, libfuse3 elsewhere.
fuser = { version = "0.16", features = ["libfuse"] }

[target.'cfg(windows)'.dependencies]
# Dokan (MIT) Rust bindings for Windows user-mode filesystem mounting. The
# Dokany runtime (dual LGPL/MIT) is installed separately, like FUSE on Unix, so
# only MIT code enters the dependency graph.
dokan = "0.3"
# Must match the widestring version dokan re-exports in its trait signatures
# (dokan 0.3.1 uses widestring 0.4); a 1.x here is a distinct, incompatible type.
widestring = "0.4"

[lints.rust]
unsafe_code = "forbid"

[lints.clippy]
all = { level = "warn", priority = -1 }
pedantic = { level = "warn", priority = -1 }
correctness = { level = "deny", priority = -1 }
suspicious = { level = "deny", priority = -1 }
# Panic-free: the images this mounts are untrusted, attacker-controllable input
# (Paranoid Gatekeeper, ADR-0012).
unwrap_used = { level = "deny", priority = 0 }
expect_used = { level = "deny", priority = 0 }
# priority 1 so these individual allows outrank the grouped warn/deny entries above.
module_name_repetitions = { level = "allow", priority = 1 }
must_use_candidate = { level = "allow", priority = 1 }
missing_errors_doc = { level = "allow", priority = 1 }
missing_panics_doc = { level = "allow", priority = 1 }
cast_possible_truncation = { level = "allow", priority = 1 }
cast_possible_wrap = { level = "allow", priority = 1 }
cast_sign_loss = { level = "allow", priority = 1 }
too_many_lines = { level = "allow", priority = 1 }
needless_pass_by_value = { level = "allow", priority = 1 }
similar_names = { level = "allow", priority = 1 }

# Debian package metadata (cargo-deb). The mount backend is FUSE, so the runtime
# needs libfuse3 + fusermount3: `$auto` keeps cargo-deb's shlibdeps autodetection
# (adds libfuse3-3) and we add `fuse3` explicitly so fusermount3 is guaranteed
# present. Only the `4n6mount` binary ships; `mkdump` is a memory-feature example.
[package.metadata.deb]
depends = "$auto, fuse3"
section = "utils"
priority = "optional"
extended-description = "Mount forensic disk images, archives, and memory dumps as a read-only filesystem with a copy-on-write overlay, deleted-file recovery, timelines, and hash filtering. Requires FUSE (fuse3) at runtime."
assets = [
    ["target/release/4n6mount", "usr/bin/", "755"],
    ["README.md", "usr/share/doc/4n6mount/README.md", "644"],
    ["LICENSE", "usr/share/doc/4n6mount/LICENSE", "644"],
]