Skip to main content

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(
&registry,
"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​

InputUse caseCompanion behavior
ParserInput::PathA file exported or acquired on the host filesystem.SQLite parsers automatically copy adjacent -wal and -shm files when present.
ParserInput::BytesSmall in-memory evidence or tests.Contains only the supplied primary bytes.
ParserInput::ReadSeekA record streamed directly from exhume_filesystem.Contains only the primary stream.
ParserInput::CompoundIndexed 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.