Skip to content

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.

Published on 7 min read

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

ArtifactPath (per user)Content
History~/Library/Safari/History.dbVisited URLs, visit times, titles, redirects
Downloads~/Library/Safari/Downloads.plistDownload URL, local path, size, dates
Bookmarks and Reading List~/Library/Safari/Bookmarks.plistBookmarks tree, Reading List items with added/viewed dates
Last session~/Library/Safari/LastSession.plistWindows and tabs open at last quit, used to restore
Recently closed tabs~/Library/Safari/RecentlyClosedTabs.plistTabs closed recently, where present
iCloud tabs~/Library/Containers/com.apple.Safari/Data/Library/Safari/CloudTabs.dbTabs 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 include id, url, domain_expansion and visit_count.
  • history_visits: one row per visit. Key columns include id, history_item (foreign key to history_items.id), visit_time, title, load_successful, redirect_source and redirect_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.

BrowserHistory locationKey tablesTimestamp format
Safari~/Library/Safari/History.dbhistory_items, history_visitsMac Absolute Time, seconds since 2001-01-01
Chrome~/Library/Application Support/Google/Chrome/<Profile>/Historyurls, visits, downloads, downloads_url_chainsWebKit/Chrome time, microseconds since 1601-01-01
Firefox~/Library/Application Support/Firefox/Profiles/<profile>/places.sqlitemoz_places, moz_historyvisitsPRTime, 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_tombstones and 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-wal can 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 .schema on 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.

Related guides