Safari Forensics on macOS: History.db, Downloads and Tabs
Analyze Safari History.db, Downloads.plist, bookmarks and session files on macOS, plus Chrome and Firefox locations, epochs and SQL queries.
Browser history is often the fastest way to establish intent and timeline in an investigation: which sites a user visited, what they searched for, where a malicious file came from and when it was downloaded. On macOS, Safari is the default browser and its artifacts are scattered across a SQLite database, several property lists and container directories. This article walks through the Safari files that matter, how to query them correctly, and the equivalent locations and epochs for Chrome and Firefox so you can build a single browsing timeline.
Access requirements
Since macOS Mojave, ~/Library/Safari is protected by TCC. Listing it from Terminal without Full Disk Access returns Operation not permitted, even with sudo. On a live system, grant Full Disk Access to the terminal or collection tool (Aftermath, UAC, mac_apt) before collection; on a dead-box image mounted on an analysis machine, TCC does not apply to the evidence volume. See TCC database forensics and forensic acquisition for details.
Always copy SQLite databases together with their -wal and -shm files. Recent visits frequently exist only in the write-ahead log until a checkpoint occurs, and opening the database in place can trigger a checkpoint that changes the evidence.
mkdir -p /cases/MBP-IR-01/safari
cp -p ~/Library/Safari/History.db* /cases/MBP-IR-01/safari/
shasum -a 256 /cases/MBP-IR-01/safari/*
Safari artifact map
| Artifact | Path (per user) | Content |
|---|---|---|
| History | ~/Library/Safari/History.db | Visited URLs, visit times, titles, redirects |
| Downloads | ~/Library/Safari/Downloads.plist | Download URL, local path, size, dates |
| Bookmarks and Reading List | ~/Library/Safari/Bookmarks.plist | Bookmarks tree, Reading List items with added/viewed dates |
| Last session | ~/Library/Safari/LastSession.plist | Windows and tabs open at last quit, used to restore |
| Recently closed tabs | ~/Library/Safari/RecentlyClosedTabs.plist | Tabs closed recently, where present |
| iCloud tabs | ~/Library/Containers/com.apple.Safari/Data/Library/Safari/CloudTabs.db | Tabs open on other devices signed into the same Apple Account; older releases kept it in ~/Library/Safari |
| Tabs (recent Safari) | ~/Library/Containers/com.apple.Safari/Data/Library/Safari/ | Newer tab and tab group storage such as SafariTabs.db; verify on your target version |
| Container data | ~/Library/Containers/com.apple.Safari/Data/Library/ | Caches, preferences and WebKit data for the sandboxed app |
| Profiles | ~/Library/Containers/com.apple.Safari/Data/Library/Safari/Profiles/<UUID>/ | Per-profile History.db and related files (Safari 17+); the default profile stays in ~/Library/Safari |
Safari's preferences have moved into the container on recent versions (~/Library/Containers/com.apple.Safari/Data/Library/Preferences/com.apple.Safari.plist), so settings such as the history retention period or the default download folder should be read from there.
History.db
Core tables
History.db is a SQLite database. The two tables you will use most are:
history_items: one row per unique URL. Key columns includeid,url,domain_expansionandvisit_count.history_visits: one row per visit. Key columns includeid,history_item(foreign key tohistory_items.id),visit_time,title,load_successful,redirect_sourceandredirect_destination.
Other tables are worth checking. history_tombstones records history that was deleted by the user (for example through "Clear History"), typically with the affected URL or time range, which is useful evidence of deliberate clearing. The origin column in history_visits is commonly used to distinguish local visits from visits synced from another device via iCloud; confirm the value meanings on a test system of the same version before stating them in a report. Inspect the schema of the actual file rather than assuming:
sqlite3 /cases/MBP-IR-01/safari/History.db '.tables'
sqlite3 /cases/MBP-IR-01/safari/History.db '.schema history_visits'
Timestamps
visit_time is a floating-point value in Mac Absolute Time: seconds since 2001-01-01 00:00:00 UTC. Add 978307200 to obtain a Unix timestamp.
Building a visit timeline
SELECT
datetime(v.visit_time + 978307200, 'unixepoch') AS visit_utc,
i.url,
v.title,
i.visit_count,
v.load_successful,
v.redirect_source,
v.redirect_destination
FROM history_visits AS v
JOIN history_items AS i ON v.history_item = i.id
ORDER BY v.visit_time;
To narrow to a window of interest, compare against a converted boundary:
SELECT datetime(v.visit_time + 978307200, 'unixepoch') AS visit_utc, i.url
FROM history_visits AS v
JOIN history_items AS i ON v.history_item = i.id
WHERE v.visit_time BETWEEN
strftime('%s', '2026-09-01 00:00:00') - 978307200
AND strftime('%s', '2026-09-02 00:00:00') - 978307200
ORDER BY v.visit_time;
Redirect chains matter: phishing and malware delivery often pass through several hops. Following redirect_source and redirect_destination (which reference other visit IDs) reconstructs the path from the link the user clicked to the final landing page.
Search engine queries are usually recoverable from the URL itself, for example the q= parameter of a search results page.
Downloads.plist
Downloads.plist is a binary property list with a DownloadHistory array. Each entry typically contains keys such as DownloadEntryURL, DownloadEntryPath, DownloadEntryProgressTotalToLoad, DownloadEntryDateAddedKey and DownloadEntryDateFinishedKey.
plutil -p /cases/MBP-IR-01/safari/Downloads.plist
The list only reflects what Safari still remembers: by default Safari removes download list items after one day, and it can also be set to remove them upon successful download or when Safari quits, and users can clear the list manually. Corroborate every download with the file itself (its com.apple.metadata:kMDItemWhereFroms and com.apple.quarantine attributes, covered in Spotlight forensics) and with the QuarantineEventsV2 database, which usually survives list clearing.
Bookmarks, Reading List and sessions
Bookmarks.plist stores a tree of Children dictionaries. Bookmark leaves have a URLString and a URIDictionary with the title. The Reading List folder (com.apple.ReadingList) entries carry a ReadingList dictionary with values such as DateAdded and, when read, DateLastViewed, which gives dated evidence of interest in specific pages.
LastSession.plist and the tab stores show what was open at the last quit or is currently open, which can reveal pages that were open for a long time but visited only once. CloudTabs.db lists tabs open on the user's other Apple devices, which can link a Mac to activity on an iPhone or iPad; treat such entries carefully, since they describe a different device.
Chrome and Firefox on macOS
Investigations rarely involve a single browser. The table below gives the default locations and epochs.
| Browser | History location | Key tables | Timestamp format |
|---|---|---|---|
| Safari | ~/Library/Safari/History.db | history_items, history_visits | Mac Absolute Time, seconds since 2001-01-01 |
| Chrome | ~/Library/Application Support/Google/Chrome/<Profile>/History | urls, visits, downloads, downloads_url_chains | WebKit/Chrome time, microseconds since 1601-01-01 |
| Firefox | ~/Library/Application Support/Firefox/Profiles/<profile>/places.sqlite | moz_places, moz_historyvisits | PRTime, microseconds since 1970-01-01 |
Chrome profiles are named Default, Profile 1 and so on. Other Chromium browsers (Edge, Brave, Arc) use the same schema under their own Application Support directories.
-- Chrome: visits with converted times
SELECT
datetime(v.visit_time / 1000000 - 11644473600, 'unixepoch') AS visit_utc,
u.url,
u.title,
v.transition
FROM visits AS v
JOIN urls AS u ON v.url = u.id
ORDER BY v.visit_time;
-- Chrome: downloads
SELECT
datetime(start_time / 1000000 - 11644473600, 'unixepoch') AS start_utc,
target_path, tab_url, referrer, received_bytes, total_bytes
FROM downloads
ORDER BY start_time;
-- Firefox: visits with converted times
SELECT
datetime(h.visit_date / 1000000, 'unixepoch') AS visit_utc,
p.url,
p.title,
h.visit_type
FROM moz_historyvisits AS h
JOIN moz_places AS p ON h.place_id = p.id
ORDER BY h.visit_date;
Chrome locks its History file while running; copy it (together with any History-journal or -wal file present) rather than opening it in place. Chrome's transition column is a bitmask whose low byte encodes the core transition type (typed, link, reload and so on); decode it before interpreting it.
Tools
sqlite3 and plutil are sufficient for most work. For broader processing, mac_apt has a Safari plugin and support for other browsers (check the plugin list of the version you run), and APOLLO (mac4n6) includes modules for Safari history. To review a copied History.db alongside Chrome or Firefox profiles without installing anything, Browser Forensics parses them in the browser into one timeline, and the files never leave your machine. Whatever tool you use, spot-check a few rows manually against the raw database to validate epoch conversion.
Pitfalls and caveats
- Private browsing. Private windows do not write to
History.db. Downloads made in private windows still land on disk and may still carry quarantine and origin metadata, and the quarantine database may record the event. Absence of history is not absence of activity. - Retention. Safari removes history items after a configurable period (one year by default; "Manually" keeps history until the user clears it), and users can clear history. Look at
history_tombstonesand database free pages for signs of deletion. - iCloud sync. Visits synced from another device can appear in
History.db. Attribute visits to the Mac only after checking the sync-related columns and corroborating with local artifacts. - WAL files. Forgetting
History.db-walcan hide the most recent visits entirely. - Time zones. All conversions above produce UTC. Convert to local time only in the final report, and state the offset used.
- Schema drift. Column names and auxiliary tables change between Safari releases. Always run
.schemaon the collected database before running canned queries.
Frequently asked questions
What epoch does Safari History.db use?
visit_time in history_visits is stored in Mac Absolute Time, seconds since 2001-01-01 00:00:00 UTC. Add 978307200 to convert it to a Unix timestamp, for example datetime(visit_time + 978307200, 'unixepoch') in sqlite3.
Why do I get 'Operation not permitted' when reading ~/Library/Safari?
The Safari data directory is protected by TCC. The process reading it, such as Terminal or your collection tool, needs Full Disk Access; running as root is not enough on its own.
Does Safari Private Browsing leave any traces?
Private windows do not write visits to History.db, but other artifacts may remain, such as downloaded files with quarantine and kMDItemWhereFroms metadata, QuarantineEventsV2 records and system logs. Treat the absence of history as inconclusive rather than proof of no activity.