A from-scratch shell command-history reader and a graded anomaly auditor — parse bash, zsh, fish, and PowerShell PSReadLine history straight off disk and surface the history clearing, back-dated entries, and download-pipe-to-shell payloads that an attacker hoped you would scroll past.
Two crates, one workspace:
shellhist-core— the reader: bash (.bash_history,#<epoch>+ multi-line), zsh (.zsh_history,EXTENDED_HISTORY: start:elapsed;cmd+ backslash continuation), fish (fish_history, nearly-YAML with a 2-rule unescape), and PowerShell PSReadLine (ConsoleHost_history.txt, backtick continuation) into one uniform [HistoryEntry] stream. Pure Rust, nounsafe, no regex engine — reads a history file authored on any OS.shellhist-forensic— the auditor: turns the parsed entry stream into severity-gradedforensicnomicon::report::Findings, so a host's shell history aggregates uniformly with the rest of the forensic fleet.
[dependencies]
shellhist-forensic = "0.1" # pulls in shellhist-coreuse shellhist_core::parse_auto;
use shellhist_forensic::{audit, source};
// Bytes off disk + an optional filename hint; the format is auto-detected.
let entries = parse_auto(history_bytes, Some(".bash_history"));
for anomaly in audit(&entries) {
let finding = anomaly.to_finding(source("host"));
println!("[{:?}] {} — {}", finding.severity, finding.code, finding.note);
// e.g. [Some(Medium)] SHELLHIST-REMOTE-EXEC-PIPE — the command "curl … | sh" downloads and pipes …
}parse_auto sniffs the format from the bytes (the filename only disambiguates ties); audit grades what it finds. Malformed or truncated history never panics — it degrades to plain lines.
Skip the two-step and get findings directly:
use shellhist_core::parse_auto;
use shellhist_forensic::audit_findings;
let entries = parse_auto(history_bytes, Some("ConsoleHost_history.txt"));
let findings = audit_findings(&entries, "host"); // Vec<forensicnomicon::report::Finding>Each anomaly is an observation ("consistent with …"); the examiner draws the conclusions. Codes are a stable, published contract.
| Code | Severity | Category | What it observes |
|---|---|---|---|
SHELLHIST-HISTORY-DISABLED |
Medium | Concealment | A surviving command that disables or clears history (unset HISTFILE, history -c, …) — consistent with anti-forensic history tampering (MITRE T1070.003) |
SHELLHIST-TIMESTAMP-REGRESSION |
Medium | Integrity | An entry whose epoch precedes its predecessor's — history went backwards in time, consistent with injected or back-dated entries |
SHELLHIST-REMOTE-EXEC-PIPE |
Medium | Threat | A download piped straight into a shell (curl … | sh) — consistent with remote payload execution (MITRE T1059 / T1105) |
SHELLHIST-PWSH-ENCODED-CMD |
Medium | Threat | An encoded or policy-bypassing PowerShell invocation — consistent with obfuscated execution (MITRE T1059.001 / T1027) |
audit(&entries) returns the typed [HistAnomaly] stream; each anomaly emits a graded report::Finding via to_finding(source), and audit_findings(&entries, scope) does both in one call. source(scope) stamps the analyzer provenance.
shellhist-core decodes each format's quirks and normalizes them into a single [HistoryEntry] (shell, command, timestamp, elapsed, paths):
use shellhist_core::{parse_auto, detect, Shell};
// Detect the format without committing to a parse…
assert_eq!(detect(b": 1700000000:0;ls", None), Shell::Zsh);
// …or just parse. Multi-line commands keep their embedded newlines; zsh
// EXTENDED_HISTORY entries carry both `timestamp` and `elapsed`.
let entries = parse_auto(b": 1700000000:5;make build\n", None);
assert_eq!(entries[0].command, "make build");
assert_eq!(entries[0].elapsed, Some(5));Per-format entry points (shellhist_core::{bash, zsh, fish, powershell}::parse) are available when the shell is already known.
A history file looks like plain text, but each shell encodes timestamps, multi-line commands, and elapsed time differently — and an attacker's traces hide in exactly those seams. This workspace answers the questions a digital forensics examiner actually needs:
| Capability | cat / a line splitter |
this workspace |
|---|---|---|
| Plain one-command-per-line read | ✅ | ✅ |
bash #<epoch> timestamp + multi-line commands |
— | ✅ |
zsh EXTENDED_HISTORY (: start:elapsed;cmd) + backslash continuation |
— | ✅ |
| fish nearly-YAML records + path association | — | ✅ |
| PowerShell PSReadLine backtick continuation | — | ✅ |
| Auto-detect format from bytes (filename only breaks ties) | — | ✅ |
History-clearing / unset HISTFILE detection |
— | ✅ |
| Timestamp-regression (back-dating) detection | — | ✅ |
| Download-pipe-to-shell detection | — | ✅ |
| Encoded-PowerShell detection | — | ✅ |
Severity-graded report::Finding output |
— | ✅ |
Pure Rust, #![forbid(unsafe_code)] |
— | ✅ |
shellhist-forensic is built for untrusted history files from potentially compromised systems:
#![forbid(unsafe_code)]across both crates — no FFI, no C bindings. It reads a history file authored on any OS.- Panic-free on malicious input — parsing is lenient (lossy UTF-8) and bounds-checked; the workspace denies
clippy::unwrap_usedandclippy::expect_usedin production code. A truncated or garbled file degrades to plain lines, never a crash. - Fuzzed — five
cargo-fuzztargets (bash,zsh,fish,powershell, andforensicfor the full parse→audit pipeline); afuzz.ymlCI workflow builds and smoke-runs each. - Validated on real artifacts — the analyzer is exercised end-to-end against a history file generated by an actual
bashsubshell (not a synthetic fixture), with its plantedcurl … | shandunset HISTFILEtraces re-surfaced (seeforensic/tests/real_data.rs).
cargo test
cargo +nightly fuzz run forensic # requires nightly + cargo-fuzzshellhist-core is the shell command-history foundation for the SecurityRonin forensic family. It sits in the PARSER layer — interpreting artifact records as forensic meaning — and feeds graded findings into issen for cross-artifact correlation. Related fleet crates:
| Crate | Role |
|---|---|
forensicnomicon |
KNOWLEDGE — the shared report::Finding model every analyzer emits |
issen |
Orchestrator — wires every forensic path and correlates findings |
ntfs-forensic |
NTFS filesystem reader + anomaly auditor |
Privacy Policy · Terms of Service · © 2026 Security Ronin Ltd