Skip to content

User activityNetwork

Safari History.db: macOS Browsing History

Safari History.db, profile databases, Downloads.plist and tab stores on macOS: which pages a user visited, when, and what was deleted.

Location
~/Library/Safari/History.db
Proves
Which URLs a macOS user opened in Safari, when, through which redirects, and whether history was cleared
Timestamps
Mac absolute time (Cocoa, seconds since 2001-01-01 UTC), stored as REAL
Access
Full Disk Access for the reading process (TCC); root alone is not enough
Retention
User setting, one year by default or kept until cleared manually; download list defaults to one day
Collection
Aftermath, mac_apt, UAC

What it is

Safari keeps its browsing history in a SQLite database named History.db. Each unique URL gets one row in history_items and each individual page load gets one row in history_visits, so the database answers both "has the user ever been here" and "exactly when, and how many times". Since Safari 17 introduced profiles, the default profile still writes to ~/Library/Safari/History.db while every additional profile has its own History.db inside the Safari sandbox container.

Around the history database sit several companion files: Downloads.plist (the download list), SafariTabs.db (open tabs, tab groups and profile names), CloudTabs.db (tabs open on the user's other iCloud devices), RecentlyClosedTabs.plist and, on some versions, LastSession.plist. This page is the quick reference; the Safari forensics guide walks through the analysis in more depth.

Where it lives

FilePathVersions / notes
History (default profile)~/Library/Safari/History.db (+ -wal, -shm, -lock)10.10 Yosemite and later; History.plist before that
History (extra profiles)~/Library/Containers/com.apple.Safari/Data/Library/Safari/Profiles/<UUID>/History.dbSafari 17+
Downloads~/Library/Safari/Downloads.plistBinary plist
Tabs, tab groups, profile names~/Library/Containers/com.apple.Safari/Data/Library/Safari/SafariTabs.dbSafari 15+ container layout
iCloud tabs~/Library/Containers/com.apple.Safari/Data/Library/Safari/CloudTabs.dbOlder releases kept it in ~/Library/Safari
Recently closed tabs~/Library/Safari/RecentlyClosedTabs.plistPresent when tabs were closed
Last session~/Library/Safari/LastSession.plistNot present on every version
Top sites~/Library/Safari/TopSites.plist and per-profile TopSites.plist
Tab snapshots~/Library/Containers/com.apple.Safari/Data/Library/Caches/com.apple.Safari/TabSnapshots/Metadata.dbCached page thumbnails
Preferences~/Library/Containers/com.apple.Safari/Data/Library/Preferences/com.apple.Safari.plistOlder: ~/Library/Preferences/com.apple.Safari.plist

Protection: ~/Library/Safari and the Safari container are TCC-protected (Mojave and later). A process without Full Disk Access gets Operation not permitted, even under sudo. On a mounted dead-box image TCC does not apply.

What it proves

  • Which URLs were loaded in Safari and at what time, per visit.
  • Whether a load succeeded (load_successful), and the redirect chain that led to a landing page (redirect_source / redirect_destination).
  • Page titles as they were at visit time (history_visits.title), and search terms inside search-result URLs.
  • That the user deliberately cleared history: history_tombstones records the cleared time range or URL.
  • Which files Safari downloaded and where they were saved (Downloads.plist), while the list has not been cleared.
  • Which tabs were open, pinned or grouped, and which profile they belonged to (SafariTabs.db).

It does not prove:

  • That the person at the keyboard read the page, or that a visit happened on this Mac: iCloud history sync can bring in visits from other devices (see the origin column, and validate its values on a reference system before relying on them).
  • Private Browsing activity: private windows do not write visits to History.db.
  • Activity in other browsers (see Chrome and Firefox).

Key fields

Table / fileFieldMeaning
history_itemsid, urlUnique URL (one row per URL)
history_itemsdomain_expansionShort site label derived from the domain
history_itemsvisit_countAggregate visit counter
history_visitshistory_itemForeign key to history_items.id
history_visitsvisit_timeTime of the visit (Mac absolute time, REAL)
history_visitstitlePage title at visit time
history_visitsload_successful, http_non_getLoad result, non-GET request flag
history_visitsredirect_source, redirect_destinationIDs of linked visits in a redirect chain
history_visitsorigin, generationSync-related bookkeeping (local vs synced)
history_tombstonesstart_time, end_time, urlRange or URL removed by a history clear
Downloads.plistDownloadHistory array: DownloadEntryURL, DownloadEntryPath, DownloadEntryDateAddedKey, DownloadEntryDateFinishedKeySource, destination, start and finish of each download

Newer builds add columns (for example status_code in history_items, udid in history_tombstones). Run .schema on the actual file first.

Timestamps

visit_time, start_time and end_time are floating-point Mac absolute time: seconds since 2001-01-01 00:00:00 UTC. Add 978307200 to get a Unix timestamp. Plist dates in Downloads.plist are native plist date objects, which plutil -p prints in UTC.

SELECT datetime(v.visit_time + 978307200, 'unixepoch') AS visit_utc,
       i.url, v.title, v.load_successful, v.redirect_source, v.redirect_destination
FROM history_visits v
JOIN history_items i ON i.id = v.history_item
ORDER BY v.visit_time;

Retention

  • History is pruned according to the user's "Remove history items" setting in Safari settings. The default is after one year, and "Manually" keeps history until the user clears it.
  • "Clear History" removes rows and leaves history_tombstones entries behind.
  • The download list has its own removal setting and is frequently empty: Velociraptor's Safari downloads artifact notes a 24-hour default. The downloaded files, their quarantine attributes and the quarantine database usually outlive the list.
  • Deleted rows may survive for a while in SQLite free pages and in the -wal file until a checkpoint or vacuum.

Collection

Grant Full Disk Access to Terminal or the collector first. Copy each database together with its -wal and -shm files.

# Live, per user, preserving metadata
ditto ~/Library/Safari /cases/host01/Safari
ditto ~/Library/Containers/com.apple.Safari/Data/Library/Safari /cases/host01/Safari-container
  • UAC: its safari.yaml artifact collects History*, Downloads.plist, SafariTabs.db*, CloudTabs*, LastSession.plist, RecentlyClosedTabs.plist and more from both locations.
  • Aftermath (Jamf): collects the default-profile History.db, Downloads.plist, Bookmarks.plist, LastSession.plist and UserNotificationPermissions.plist (no extensions, no extra profiles); run as root with Full Disk Access.
  • mac_apt: the SAFARI plugin exports and parses the files from a disk image or live mount, including Safari 17 profile databases.

Parsing

  • sqlite3 for History.db, SafariTabs.db and CloudTabs.db. Work on a copy (with its -wal and -shm files in the same folder), never on the evidence original, because opening a WAL database can checkpoint it.
  • plutil -p Downloads.plist for the download list.
  • mac_apt SAFARI plugin: history, downloads, bookmarks, top sites, last session, recently closed tabs, iCloud tabs and tab snapshots in one table.
  • APOLLO: the safari_history module queries History.db; its KnowledgeC Safari modules add app usage context.

Find deliberate clearing:

SELECT datetime(start_time + 978307200, 'unixepoch') AS cleared_from_utc,
       datetime(end_time + 978307200, 'unixepoch')   AS cleared_to_utc,
       url
FROM history_tombstones ORDER BY end_time;

Investigator tips

  • Always process every profile: a user who keeps a separate "Work" or "Private" profile has a second History.db in the container Profiles/<UUID>/ folder. Map UUIDs to profile names through SafariTabs.db.
  • Missing -wal files are the most common reason for "missing" recent visits.
  • Tie downloads to files on disk through the com.apple.quarantine and kMDItemWhereFroms attributes and the quarantine events database.
  • Correlate visits with KnowledgeC app focus to show Safari was actually in the foreground at the time.
  • history_tombstones rows timestamped just before an incident window are a strong anti-forensics indicator; record them even when the history itself is gone.
  • Treat CloudTabs.db entries as another device's activity until proven otherwise.

See also