Skip to content

Back to the catalog

dfir.chwriteup

Today I Learned - Zsh Sessions (even more Timestamps)

A short DFIR technical note explaining zsh's ~/.zsh_sessions directory, where each terminal session gets a paired .session/.history file (or a live .historynew tracked by $HISTFILE) keyed by TERM_SESSION_ID. The .session file contains a restored-session timestamp, giving investigators a way to recover executed commands and narrow down execution timeframes even when an attacker has deleted the main .zsh_history file.

TL;DR Zsh's ~/.zsh_sessions directory preserves per-session command history and restore timestamps, useful as a forensic artifact even if .zsh_history is deleted.
Document

What it is

A DFIR technique for recovering shell command history and timestamps from Zsh's .zsh_sessions directory on macOS/Linux, the successor artifact to fc -lf/fc -li live-response methods. The value is that these files survive deletion of .zsh_history.

How it works

.zsh_sessions sits at the root of the user's home directory. Inside, each terminal session produces two files sharing one name — the TERM_SESSION_ID (a UUID generated at session start, constant for its lifetime):

  • <UUID>.history — the commands run in that session (each pairs with a .session file of the same UUID).
  • <UUID>.session — a one-line shell command containing the reopen timestamp.

The currently active session instead has a single <UUID>.historynew file with no .session companion; $HISTFILE points to it. The .session file's timestamp is stored as a Unix epoch inside a date -r call, e.g.:

echo Restored session: "$(/bin/date -r 1715852825)"

Here 1715852825 decodes to Thu May 16 2024 09:47:05 UTC — the moment the terminal was reopened.

Preconditions / Affected

Zsh sessions must be enabled (default on modern macOS). The artifact matters when .zsh_history is missing or tampered but the per-session files remain, or when you want a timestamp that plain .zsh_history (without extended history) does not carry.

What to do

Enumerate the directory and pair histories with their session timestamps:

head 53517CEE-640B-4445-98BD-1A4BEE3FB463.history
cat 53517CEE-640B-4445-98BD-1A4BEE3FB463.session

Read the epoch from the date -r argument in each .session file to place its .history in time. Identify the live session via $HISTFILE / $TERM_SESSION_ID (the .historynew). Files with no .session pair (the active .historynew) will not have a restore timestamp.

Caveats

The .session timestamp marks only when the terminal was reopened/restored, not when individual commands ran — coarser than fc -lf on a live session. The author frames it as narrowing the window, not pinpointing execution. There is exactly one timestamp per session file, so all commands in a .history can only be bounded to that session's restore time.

Technologies named here

Techniques named here

  • Zsh session artifact review
  • Recovery of deleted .zsh_history
  • Live session history extraction