# shellhist-forensic
[](https://crates.io/crates/shellhist-forensic)
[](https://crates.io/crates/shellhist-core)
[](https://docs.rs/shellhist-forensic)
[](LICENSE)
[](https://github.com/SecurityRonin/shellhist-forensic/actions)
[](https://github.com/sponsors/h4x0r)
**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::Finding`s.**
```toml
[dependencies]
shellhist-forensic = "0.1" # pulls in shellhist-core
```
```rust
use shellhist_core::parse_auto;
use shellhist_forensic::{audit, source};
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 …
}
```
`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.
| `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`](https://crates.io/crates/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`](https://github.com/SecurityRonin/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](https://securityronin.github.io/shellhist-forensic/privacy/) · [Terms of Service](https://securityronin.github.io/shellhist-forensic/terms/) · © 2026 Security Ronin Ltd