Skip to content

FSEvents Forensics: The macOS File System Change Log

How the macOS .fseventsd logs record file creation, deletion and rename events, how to parse them, and how to estimate dates without per-record timestamps.

Published on 9 min read

FSEvents is the macOS service that tells applications "something changed under this directory". To make that work across reboots, the fseventsd daemon writes a compact, persistent change log to every writable volume it tracks. For an investigator the result is a record of file system activity by path: files and folders created, modified, renamed, removed, their permissions or extended attributes changed. It regularly answers questions other artifacts cannot, such as "did a file with this name ever exist on this Mac", "what was on that USB stick", and "what did the installer drop and then clean up". This article covers where the logs live, their structure, what the flags mean, how to parse them and, just as important, what they cannot tell you.

Where the logs live

fseventsd keeps a separate log folder named .fseventsd at the root of each volume it records. On macOS 10.15 Catalina and later, user activity is on the Data volume, so the folder that matters on an internal disk is:

/System/Volumes/Data/.fseventsd/

Removable and external volumes carry their own /Volumes/<name>/.fseventsd/. See the fseventsd glossary entry for a short definition, and APFS snapshots and timestamps for the System/Data split.

A typical listing looks like this:

sudo ls -l /System/Volumes/Data/.fseventsd/ | head
-rw-------  1 root  wheel   61234 Sep 27 09:41 000000000a3f21c7
-rw-------  1 root  wheel   48871 Sep 27 14:02 000000000a4105e2
-rw-------  1 root  wheel   12050 Sep 28 08:15 000000000a41c9d0
-rw-------  1 root  wheel      36 Sep 12 10:03 fseventsd-uuid

Each log file is named with a 16-digit hexadecimal value derived from the event IDs it covers, so names increase over time and sort in event order. The fseventsd-uuid file identifies the event stream for the volume; if it changes, the history was reset. Reading the folder requires root, and on a live system your terminal or collection tool also needs Full Disk Access.

File format

Each log file is gzip-compressed. Decompressed, it contains one or more pages. Each page starts with a short header containing a magic value that encodes the format version, followed by the page size, and then a sequence of variable-length records.

MagicFormatRecord layout (after the null-terminated path)
1SLDversion 1, older macOSevent ID (8 bytes), flags (4 bytes)
2SLDversion 2, macOS 10.13 onwardevent ID (8 bytes), flags (4 bytes), node ID (8 bytes)
3SLDversion 3, macOS 14 Sonoma onwardas version 2 plus a 4-byte field that FSEventsParser decodes as a user ID (fs_uid) and mac_apt leaves as unknown

Hedge on version boundaries: which version you see depends on the macOS release that wrote the volume, and a USB drive used on several Macs can contain files in different versions. A robust parser checks the magic on every page rather than assuming.

The fields are:

  • Path: the full path relative to the volume root, null-terminated. Paths are stored as they existed when the event was recorded, including for items that were later deleted.
  • Event ID: a 64-bit counter that increases monotonically for the volume. It gives you ordering, not time.
  • Flags: a bitmask describing what happened and to what kind of object.
  • Node ID: in version 2 and later, the file system object identifier (on APFS, effectively the inode number). Useful for tying several path names to the same object across renames.

You can peek at a file without a parser to confirm the format:

gzip -dc 000000000a41c9d0 | head -c 4; echo
gzip -dc 000000000a41c9d0 | strings | head -20

Work on a copy, never on the evidence folder itself.

Event flags

Flags are combined, so a single record can say "file, created, modified, extended attribute modified". Note that the on-disk bit values do not match the public FSEventStreamEventFlags constants from Apple's developer API, so rely on a parser that decodes the on-disk layout rather than on the SDK header. The table below lists flag names as common parsers report them; exact spelling varies between tools.

FlagMeaning for the investigator
Created / FolderCreatedA file or folder appeared at this path
RemovedThe item at this path was deleted
Renamed (RenamedOrMoved in some parsers)The item was renamed or moved; look for a paired record with the other name and the same node ID
ModifiedContent was written
InodeMetaModInode metadata changed (timestamps, flags)
PermissionChangeMode, ownership or ACL changed
ExtendedAttrModified / ExtendedAttrRemovedAn xattr such as com.apple.quarantine was added or removed
FinderInfoModFinder info changed
ExchangeTwo items swapped content, typical of safe-save patterns
ItemClonedAPFS clone operation
FileEvent / FolderEvent / SymbolicLink / HardLinkObject type
Mount / UnmountA volume was mounted or unmounted under this path
EndOfTransactionMarks the end of a group of related changes

Because FSEvents coalesces activity, one record often summarises several operations on the same path within a short window. A record flagged Created;Modified;Removed for a file in /private/tmp simply means the file was created, written and deleted before the buffer was flushed.

Dates without timestamps

FSEvents records carry no timestamps. You have to infer time.

  1. Log file modification time. Each .fseventsd file is written when fseventsd flushes its buffer, so its mtime is an upper bound for the events it contains. The mtime of the previous log file is a rough lower bound. On an active system files cover minutes to hours; on an idle volume a single file can span days.
  2. Paths that embed dates. Many system and application files carry dates in their names, such as rotated logs, diagnostic reports or Time Machine snapshot names. When such a path appears as Created in a log file, it anchors the surrounding event IDs. Some parsers compute approximate date ranges this way automatically.
  3. Correlation. Event IDs are strictly ordered per volume. If a file's APFS birth time, a Unified Logs entry, or a quarantine event gives you a real time for one path, neighbouring event IDs are close in time.

Always report FSEvents-derived times as a range or an estimate, and state which anchor you used.

Parsing tools

FSEventsParser

FSEventsParser by David Cowen (dlcowen) is a Python tool that decompresses the logs, decodes all page versions and flags, and writes results to a SQLite database, TSV report and optional custom reports driven by SQL queries in a JSON file. A typical invocation against an exported folder looks like this, but check the README of the release you download because options differ between versions:

python3 FSEParser_V4.1.py -s /cases/MBP-IR-01/fseventsd -t folder \
  -o /cases/MBP-IR-01/out -c MBP-IR-01

The output includes the source log file name and its modification time for each record, which is exactly what you need for date estimation.

mac_apt

mac_apt (ydkhatri) includes an FSEVENTS plugin and can read directly from a disk image, so you do not have to export the folder first:

python3 mac_apt.py -o /cases/MBP-IR-01/mac_apt E01 /cases/MBP-IR-01.E01 FSEVENTS

It handles the Data volume and volume group layout and writes to SQLite, with optional spreadsheet or delimited-text output depending on the version. For live triage, UAC can gather the .fseventsd folder for later parsing; Aftermath does not collect it by default, so add the folder with --collect-dirs.

To review a copied .fseventsd folder without installing anything, FSEvents Parser decodes all three page versions in the browser, derives time windows from the log files' modification times and exports CSV or Timesketch; the files stay on your machine.

Querying the output

Once parsed into SQLite, a few queries cover most cases. The examples use the FSEventsParser schema: it writes its rows to the fsevents_sorted_by_event_id table (the temporary fsevents table is dropped). mac_apt uses different column names, so adapt the queries to its output.

-- Deleted items under a user's Downloads folder
-- (the LIKE pattern also matches ExtendedAttrRemoved and LastHardLinkRemoved; refine as needed)
SELECT fullpath, flags, source
FROM fsevents_sorted_by_event_id
WHERE fullpath LIKE 'Users/analyst/Downloads/%' AND flags LIKE '%Removed%';

-- Anything touching LaunchAgents
SELECT fullpath, flags, source
FROM fsevents_sorted_by_event_id
WHERE fullpath LIKE '%/Library/LaunchAgents/%';

Use cases

  • Deleted files and folders. A Removed record is often the only remaining evidence that a file existed. Path names alone, such as Users/analyst/Desktop/export_clients.zip, can be highly probative.
  • Renamed files. Pair Renamed records by node ID to follow a file through renames.
  • Removable media. A USB drive's own .fseventsd shows what was written to it on a Mac. The internal Data volume's logs may show the drive's mount point being created under Volumes/ and files copied from it.
  • Installer and malware staging. Short-lived files in /private/tmp or /private/var/folders/, new items in Library/LaunchAgents, and quarantine xattr removal (ExtendedAttrRemoved on a freshly downloaded app) are classic signals. See launchd persistence and quarantine events.
  • Timestomping checks. An InodeMetaMod on a file whose timestamps claim to be old is worth investigating.

Limitations and pitfalls

  • No per-record time. All dates are inferred. Do not present them as exact.
  • Retention is finite. fseventsd purges old logs; history may span days to months depending on activity and disk usage. Collect early.
  • Coalescing. Several operations on one path within a flush window collapse into a single record with multiple flags. You cannot always tell the order of operations inside it.
  • Disabled logging. A no_log file inside .fseventsd disables logging for that volume. Its presence, or a recently changed fseventsd-uuid, may indicate deliberate tampering or simply a volume formatted elsewhere.
  • Read-only and network volumes. No log is written to volumes mounted read-only, and network shares are not covered.
  • Not every write is seen. Some system paths are excluded, and a hard shutdown can lose unflushed events.
  • Paths are volume-relative. On an internal disk, Users/analyst/... in the Data volume log maps to /Users/analyst/... through firmlinks.
  • Evidence handling. Copy the entire folder, preserve log file modification times (they are your only time source), and hash before parsing.

Frequently asked questions

Do FSEvents records contain timestamps?

No. Individual records hold a path, an event ID, flags and, in newer formats, a node ID. Dates are estimated from the modification time of the log file that contains the record and from paths in the same file that embed their own dates.

Can FSEvents prove a deleted file existed?

It can show that a path was created, modified or removed on that volume, which is often the only surviving trace of a deleted file or folder. It does not preserve file content, so pair it with snapshots, backups or carving if you need the data itself.

Do USB drives have their own FSEvents logs?

Usually yes. Writable volumes mounted on macOS normally get their own .fseventsd folder at the volume root, unless logging was disabled with a no_log file or the volume was mounted read-only. That folder travels with the drive and can link it to activity on a Mac.

Related guides