# forensic-carve
[](https://crates.io/crates/forensic-carve)
[](https://docs.rs/forensic-carve)
[](https://www.rust-lang.org)
[](LICENSE)
[](https://github.com/SecurityRonin/forensic-carve/actions/workflows/ci.yml)
[](https://github.com/rust-secure-code/safety-dance/)
[](fuzz/)
**One carving contract, one single-pass sweep engine — the same carver recovers a
deleted artifact from unallocated disk *and* from a memory image.**
`forensic-carve` is the SecurityRonin fleet's carving foundation. A per-format
**carver** sees only `&[u8]` windows, so it is medium-agnostic by construction; the
**sweep engine** runs one aho-corasick detection pass over supplied regions (disk
unallocated extents or memory VAD regions), materializes only the window each hit
needs (detection ≠ materialization — a large artifact is never truncated to a scan
chunk), and hands it to the matching carver.
## A carver is tiny
```rust
use forensic_carve::{Carver, Signature, CarveContext, CarvedItem, RecoveryMethod};
struct SqliteCarver;
impl Carver for SqliteCarver {
fn format(&self) -> &'static str { "sqlite" }
fn signatures(&self) -> &[Signature] {
const S: &[Signature] = &[Signature::new(b"SQLite format 3\0", 0)];
S
}
fn max_window(&self) -> u64 { 1 << 30 }
fn carve(&self, window: &[u8], ctx: &CarveContext) -> Vec<CarvedItem> {
// validate the header, bound the DB, then:
vec![CarvedItem::artifact_bytes(
"sqlite", ctx.base_offset(), 0.9,
ctx.recovery_method(), // echoed — UnallocatedCarve on disk, MemoryCarve in RAM
window.to_vec(),
)]
}
}
```
## One sweep, any medium
```rust
use forensic_carve::{sweep, Region, CarveOptions, RecoveryMethod, registered_carvers};
let opts = CarveOptions { recovery_method: RecoveryMethod::UnallocatedCarve, ..Default::default() };
let items = sweep(&source, unallocated_regions, ®istered_carvers(), &opts);
// each item: the medium-neutral CarvedItem + its source offset + the region's attribution tag
```
`source` is any `RegionSource` (a positioned `read_at`): a disk `ImageSource`, or a
memory `VirtualAddressSpace` whose `read_virt` un-scatters physically-discontiguous
pages. Carvers register themselves with `inventory::submit!`, so a binary that
force-links them collects the whole set via `registered_carvers()` — the consumer
never depends on the parser crates.
## The recovery vocabulary
Every carved item carries a `RecoveryMethod` — *how* it was recovered, set by which
sweep ran (carving **is** a recovery method):
| `Tombstone` | a deletion the filesystem recorded (`--deleted`) |
| `FileInternalCarve` | a located artifact's own slack (freelist/WAL/`ElfChnk`) |
| `UnallocatedCarve` | whole-image unallocated carving (`--unallocated`) |
| `MemoryCarve` | a memory image (process VA region or physical frame) |
## Trust, but verify
- **Input-fuzzed.** `fuzz_sweep` drives arbitrary bytes, out-of-range/zero-length
regions, and arbitrary chunk sizes through the engine (138k+ execs, 0 crashes).
- **Panic-free by lint.** `unwrap_used`/`expect_used` are denied in production; the
engine returns *no items* on any malformed input rather than panicking.
- **`#![forbid(unsafe_code)]`** — no `unsafe`, anywhere.
Governing design: SecurityRonin fleet ADR 0001 (*carving — one flag taxonomy, one
sweep engine, one contract*).