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
Tools
Compare all tools- Quarantine ParserIn browser
- mac_aptCLI · open source
- PlasoCLI · open source
- VelociraptorPlatform · open source
- sqlite3CLI · built into macOS
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
| Artifact | Path | Notes |
|---|---|---|
| Quarantine database | ~/Library/Preferences/com.apple.LaunchServices.QuarantineEventsV2 | One per user, SQLite, single table LSQuarantineEvent |
| Quarantine xattr | com.apple.quarantine on the file itself | Stored 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 releases | Written 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:
| Column | Meaning |
|---|---|
LSQuarantineEventIdentifier | UUID (primary key), matches the last field of the xattr |
LSQuarantineTimeStamp | Event time, Mac absolute time (REAL) |
LSQuarantineAgentBundleIdentifier | Bundle ID of the downloading app |
LSQuarantineAgentName | Display name of the agent |
LSQuarantineDataURLString | URL of the downloaded data |
LSQuarantineOriginURLString | Referring or origin page |
LSQuarantineOriginTitle | Origin title, when recorded |
LSQuarantineSenderName, LSQuarantineSenderAddress | Sender, filled by agents such as Mail |
LSQuarantineTypeNumber | Launch Services quarantine type (web download, email attachment, and so on) |
LSQuarantineOriginAlias | Alias 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. Add978307200for 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
QUARANTINEplugin (database and.LastGKReject):python mac_apt.py -o out MOUNTED /Volumes/evidence QUARANTINE. - Plaso
ls_quarantineplugin: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
curlleaves no quarantine trace; pivot to shell history, unified logs and FSEvents. - Correlate data URLs with Safari history or Chrome/Firefox and with
kMDItemWhereFromsin the Spotlight store. - The flag change from
0083to00c3sets0x0040(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.