shellhist-forensic
Point it at a shell-history file, get back severity-graded anomalies — history clearing, back-dated entries, download-pipe-to-shell payloads, and encoded PowerShell as forensicnomicon::report::Findings.
[]
= "0.1" # pulls in shellhist-core
use parse_auto;
use ;
let entries = parse_auto;
for anomaly in audit
audit grades an entry stream; audit_findings(&entries, scope) does the parse-to-Finding conversion in one call. Malformed history degrades to plain lines upstream in shellhist-core, never a panic.
The anomaly codes
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). source(scope) stamps the analyzer provenance.
The two-crate split
This crate is the analyzer; the reader is shellhist-core (bash, zsh EXTENDED_HISTORY, fish, and PowerShell PSReadLine into one uniform HistoryEntry stream, with parse_auto / detect and per-format parse entry points). The split mirrors ntfs-core/ntfs-forensic. Together they feed issen for cross-artifact correlation.
Trust, but verify
Built for untrusted history files from potentially compromised systems: #![forbid(unsafe_code)]; panic-free on crafted input (the workspace denies clippy::unwrap_used / expect_used in production code, parsing is lenient and bounds-checked); fuzzed with five cargo-fuzz targets (one per format plus the full parse→audit pipeline) and exercised end-to-end against a history file generated by a real bash subshell, with its planted traces re-surfaced.
Privacy Policy · Terms of Service · © 2026 Security Ronin Ltd