Exhume Artefacts as a library
Embed exhume_artefacts when a Rust application needs streaming parser output,
compound evidence inputs, timeline extraction, or custom parsers.
Install​
cargo add exhume_artefacts
cargo add anyhow
Run a registered parser​
use exhume_artefacts::parsers::build_registry;
use exhume_artefacts::{run_parser_by_name, ObjectParsed, ParserInput};
fn parse_event_log(path: &str) -> anyhow::Result<()> {
let registry = build_registry();
let mut sink = |object: ObjectParsed| {
println!("{}\t{}\t{}", object.parser, object.kind, object.json);
Ok(())
};
run_parser_by_name(
®istry,
"windows_evtx",
ParserInput::Path(path.into()),
&mut sink,
)
}
Objects are streamed to the sink instead of being accumulated by the crate. The caller controls memory use, persistence, filtering, and backpressure. If the sink returns an error, parsing stops and that error is returned.
Input forms​
| Input | Use case | Companion behavior |
|---|---|---|
ParserInput::Path | A file exported or acquired on the host filesystem. | SQLite parsers automatically copy adjacent -wal and -shm files when present. |
ParserInput::Bytes | Small in-memory evidence or tests. | Contains only the supplied primary bytes. |
ParserInput::ReadSeek | A record streamed directly from exhume_filesystem. | Contains only the primary stream. |
ParserInput::Compound | Indexed evidence with source metadata and resolved companions. | Carries primary and companion roles through a caller-provided ParserFileProvider. |
Use Compound when a parser declares CompanionSpec entries or when
requires_source_metadata() is true. Companion rules can append a suffix to
the primary path or select a named sibling. Each ParserSource can retain its
original path, artefact row ID, indexed file row ID, and native filesystem
identifier.
Implementing a parser​
A parser implements the object-safe Parser trait:
use anyhow::Result;
use exhume_artefacts::{ObjectParsed, Parser, ParserInput, TimelineEvent};
struct ExampleParser;
impl Parser for ExampleParser {
fn name(&self) -> &'static str {
"example_parser"
}
fn description(&self) -> &'static str {
"Parse Example application records."
}
fn run_into(
&self,
input: ParserInput,
sink: &mut dyn FnMut(ObjectParsed) -> Result<()>,
) -> Result<()> {
// Read input, preserve source values, and call sink once per object.
let _ = input;
let _ = sink;
Ok(())
}
fn extract_timeline_events(&self, _object: &ObjectParsed) -> Vec<TimelineEvent> {
Vec::new()
}
}
Then add the parser to parsers::build_registry. Parser names must be unique.
Prefer a stable normalized kind, keep native fields in the JSON provenance or
details block, preserve raw timestamps alongside normalized time, and never
silently invent values missing from the evidence.
Development examples​
The repository includes a positional standalone-file example:
cargo run -p exhume_artefacts --example parse_file -- \
/evidence/Security.evtx windows_evtx
List parsers from either example without supplying an input:
cargo run -p exhume_artefacts --example parse_file -- --list-parsers
cargo run -p exhume_artefacts --example parse_from_fs -- --list-parsers
License​
GPL-2.0-or-later.