Skip to content

File accessNetworkExecution

QuarantineEventsV2 and Quarantine xattr on macOS

QuarantineEventsV2 and the com.apple.quarantine attribute record what was downloaded, from which URL, by which app and when, often after the file is gone.

Location
~/Library/Preferences/com.apple.LaunchServices.QuarantineEventsV2
Proves
Which files a user downloaded, from which URL and origin page, with which app, and when
Timestamps
Database: Mac absolute time (UTC); xattr: hex Unix seconds (UTC)
Access
Owning user or root; Full Disk Access recommended for the collector
Retention
Until deleted or cleared (no documented automatic purge)
Collection
Aftermath, mac_apt, UAC, Velociraptor

What it is

Two linked artifacts record downloads on macOS. When an app that opts in to file quarantine (browsers, Mail, Messages, AirDrop, sandboxed apps) writes a file, macOS attaches the com.apple.quarantine extended attribute to it. Launch Services also writes a row to a per-user SQLite database, QuarantineEventsV2, holding the download URL, origin page and downloading agent.

The xattr travels with the file; the database row stays behind when the file is deleted. Together they are the standard way to answer "where did this file come from?"

Where it lives

ArtifactPathNotes
Quarantine database~/Library/Preferences/com.apple.LaunchServices.QuarantineEventsV2One per user, SQLite, single table LSQuarantineEvent
Quarantine xattrcom.apple.quarantine on the file itselfStored in AppleDouble ._ files on volumes without native xattrs
Last Gatekeeper rejection/private/var/db/SystemPolicyConfiguration/.LastGKReject on current macOS; /private/var/db/.LastGKReject on older releasesWritten by syspolicyd; mac_apt QUARANTINE looks only at the older path in an image, so pass it the current file directly

The database has kept this name and location across modern macOS releases. It is readable by its owner; collect it as root for all users, with Full Disk Access granted to the collector.

What it proves

  • A URL was downloaded by a named agent (com.apple.Safari, com.google.Chrome, com.apple.mail) at a given time.
  • The page that led to the download (LSQuarantineOriginURLString), and for mail, the sender.
  • A specific file on disk came from a specific download (UUID in the xattr equals LSQuarantineEventIdentifier).
  • Whether a quarantined file was opened and approved via Gatekeeper (user-approved flag in the xattr).

It does not record the local save path or file name, and it does not cover downloads made with curl, wget, scp, git or other tools that do not opt in. Absence of a record is weak evidence.

Key fields

LSQuarantineEvent table:

ColumnMeaning
LSQuarantineEventIdentifierUUID (primary key), matches the last field of the xattr
LSQuarantineTimeStampEvent time, Mac absolute time (REAL)
LSQuarantineAgentBundleIdentifierBundle ID of the downloading app
LSQuarantineAgentNameDisplay name of the agent
LSQuarantineDataURLStringURL of the downloaded data
LSQuarantineOriginURLStringReferring or origin page
LSQuarantineOriginTitleOrigin title, when recorded
LSQuarantineSenderName, LSQuarantineSenderAddressSender, filled by agents such as Mail
LSQuarantineTypeNumberLaunch Services quarantine type (web download, email attachment, and so on)
LSQuarantineOriginAliasAlias blob, usually empty

xattr value flags;timestamp;agent;UUID, for example 0083;66f7c2a1;Safari;<UUID>. Flag bits documented by researchers (Apple does not document them publicly) include 0x0001 download, 0x0002 sandbox, 0x0004 hard quarantine and 0x0040 user approved.

Timestamps

  • LSQuarantineTimeStamp: Mac absolute time, seconds since 2001-01-01 00:00:00 UTC. Add 978307200 for Unix time.
  • xattr timestamp: hexadecimal Unix seconds, UTC.
SELECT datetime(LSQuarantineTimeStamp + 978307200, 'unixepoch') AS event_utc,
       LSQuarantineAgentBundleIdentifier, LSQuarantineDataURLString,
       LSQuarantineOriginURLString, LSQuarantineEventIdentifier
FROM LSQuarantineEvent ORDER BY LSQuarantineTimeStamp;
xattr -p com.apple.quarantine ./sample.dmg
date -u -r $((16#66f7c2a1))

Retention

No automatic purge is documented: records typically accumulate for the life of the user profile, which makes them valuable after the downloaded file is removed. Users, cleanup tools and attackers can delete rows with sqlite3, or remove the whole file. The xattr is lost when stripped with xattr -d/xattr -c, when a file is copied to a file system or service that drops extended attributes, or when an archive is extracted by a tool that does not propagate quarantine.

Collection

# Every user's database (root, collector with Full Disk Access)
for u in /Users/*; do
  f="$u/Library/Preferences/com.apple.LaunchServices.QuarantineEventsV2"
  [ -f "$f" ] || continue
  d="./case/qe_$(basename "$u")"
  mkdir -p "$d" && sudo cp -p "$f"* "$d/"
done
# Preserve xattrs when copying suspect files
ditto --extattr ./suspect.pkg ./case/files/
  • Aftermath collects the LSQuarantine database.
  • UAC: files/system/quarantine_events.yaml (per-user database).
  • Velociraptor: MacOS.System.QuarantineEvents.
  • Dead-box: copy from the mounted Data volume; hash first, work on copies.

Parsing

  • sqlite3 with the query above.
  • mac_apt QUARANTINE plugin (database and .LastGKReject): python mac_apt.py -o out MOUNTED /Volumes/evidence QUARANTINE.
  • Plaso ls_quarantine plugin: log2timeline.py --parsers 'sqlite/ls_quarantine' --storage-file qe.plaso ./case.
  • Join a file to its record:
SELECT * FROM LSQuarantineEvent
WHERE LSQuarantineEventIdentifier = '<UUID from xattr>';

Quarantine Parser reads QuarantineEventsV2 with its -wal (and carves deleted rows), com.apple.quarantine xattr listings and .LastGKReject in the browser, and links each file on disk to its download record; nothing is uploaded.

Investigator tips

  • A suspicious binary with no quarantine xattr but a matching database record suggests the attribute was deliberately stripped.
  • A second stage fetched with curl leaves no quarantine trace; pivot to shell history, unified logs and FSEvents.
  • Correlate data URLs with Safari history or Chrome/Firefox and with kMDItemWhereFroms in the Spotlight store.
  • The flag change from 0083 to 00c3 sets 0x0040 (user approved): the item was opened and allowed. Treat flag meanings as supporting evidence.
  • Gaps in an otherwise dense timeline, or a database much smaller than the browser history suggests, can indicate manual clearing; check WAL and free pages.
  • Epoch mix-ups between the xattr (Unix, hex) and the database (Mac absolute) produce 31-year errors.
  • For Gatekeeper decisions that follow the download, see Gatekeeper and XProtect and the quarantine and Gatekeeper guide.

See also