Chrome and Firefox History on macOS
Chrome History and Firefox places.sqlite on macOS: paths, profiles, key tables and epochs to rebuild visits and downloads outside Safari.
- Location
- ~/Library/Application Support/Google/Chrome/Default/History
- Proves
- Which sites a user opened in Chrome, Chromium browsers or Firefox, when, and what they downloaded
- Timestamps
- Chrome: microseconds since 1601-01-01 UTC; Firefox PRTime: microseconds since 1970-01-01 UTC
- Access
- File owner or root; give the collector Full Disk Access as well
- Retention
- Chrome: about 90 days of visits; Firefox: size-based expiration of old pages
- Collection
- Aftermath, UAC, mac_apt
Tools
Compare all tools- Browser ForensicsIn browser
- HindsightCLI · open source
- mac_aptCLI · open source
- sqlite3CLI · built into macOS
What it is
Chrome and Firefox are the most common third-party browsers on macOS, and neither stores anything in Safari's files. Chrome, and every Chromium-based browser (Edge, Brave, Vivaldi, Opera, Arc), keeps each profile's browsing history in a SQLite file simply named History, with separate tables for URLs, visits and downloads. Firefox stores history and bookmarks together in places.sqlite inside a randomly named profile folder.
Both live in the user's ~/Library/Application Support, so they are per-user, survive app updates, and are copied intact by any collector that grabs home directories. Their epochs differ from Safari's, which is the most common source of timeline errors.
Where it lives
| Browser | Profile root on macOS | Main history file |
|---|---|---|
| Chrome | ~/Library/Application Support/Google/Chrome/ | <Profile>/History |
| Edge | ~/Library/Application Support/Microsoft Edge/ | <Profile>/History |
| Brave | ~/Library/Application Support/BraveSoftware/Brave-Browser/ | <Profile>/History |
| Vivaldi | ~/Library/Application Support/Vivaldi/ | <Profile>/History |
| Arc | ~/Library/Application Support/Arc/User Data/ | <Profile>/History |
| Opera | ~/Library/Application Support/com.operasoftware.Opera/ | History (single profile layout) |
| Firefox | ~/Library/Application Support/Firefox/Profiles/<random>.<name>/ | places.sqlite, formhistory.sqlite |
Chromium profile folders are named Default, Profile 1, Profile 2 and so on, plus Guest Profile. Each profile's Preferences (JSON) holds the profile's display name and settings. Firefox lists its profiles in ~/Library/Application Support/Firefox/profiles.ini.
Protection: the files are owned by the user, so the owner or root can read them. Grant Full Disk Access to the collector anyway, since a collection pass always touches TCC-protected locations too.
What it proves
- Every recorded visit to a URL with its time, and for Chrome the page that led to it (
from_visit) and how it was reached (transition). - Typed URLs versus clicked links (Chrome
typed_countand transition core type; Firefoxtypedandvisit_type). - Downloads: source URL, redirect chain, referrer, target path, size and start/end time (Chrome
downloads+downloads_url_chains; Firefox download annotations). - Form field entries in Firefox (
formhistory.sqlite:fieldname,value,timesUsed,firstUsed,lastUsed).
It does not prove:
- Incognito or private-window activity (not written to these databases).
- That a visit came from this Mac when browser sync is enabled; corroborate with local artifacts.
- That a downloaded file still exists, or was opened.
Key fields
| Browser | Table | Fields |
|---|---|---|
| Chrome | urls | id, url, title, visit_count, typed_count, last_visit_time, hidden |
| Chrome | visits | id, url (FK to urls.id), visit_time, from_visit, external_referrer_url, transition, visit_duration, is_known_to_sync |
| Chrome | downloads | id, guid, current_path, target_path, start_time, end_time, received_bytes, total_bytes, state, danger_type, opened, last_access_time, referrer, site_url, tab_url, mime_type |
| Chrome | downloads_url_chains | id (FK to downloads.id), chain_index, url |
| Firefox | moz_places | id, url, title, visit_count, last_visit_date, typed, hidden, frecency |
| Firefox | moz_historyvisits | id, place_id (FK to moz_places.id), visit_date, from_visit, visit_type, source |
| Firefox | moz_annos + moz_anno_attributes | Download data: downloads/destinationFileURI, downloads/metaData |
Chrome's transition is a bitmask: the low byte (transition & 0xFF) is the core type (link, typed, auto bookmark, reload and so on) and the high bits are qualifiers such as redirects.
Timestamps
| Browser | Format | Convert to Unix seconds |
|---|---|---|
| Chrome / Chromium | Integer microseconds since 1601-01-01 00:00:00 UTC ("WebKit time") | value / 1000000 - 11644473600 |
| Firefox | PRTime: integer microseconds since 1970-01-01 00:00:00 UTC | value / 1000000 |
Both are UTC. visit_duration in Chrome is a duration in microseconds, not a timestamp.
-- Chrome visits
SELECT datetime(v.visit_time/1000000 - 11644473600, 'unixepoch') AS visit_utc,
u.url, u.title, v.transition & 0xFF AS core_transition, v.from_visit
FROM visits v JOIN urls u ON u.id = v.url
ORDER BY v.visit_time;
-- Chrome downloads with full redirect chain
SELECT d.id, datetime(d.start_time/1000000 - 11644473600, 'unixepoch') AS start_utc,
d.target_path, c.chain_index, c.url
FROM downloads d JOIN downloads_url_chains c ON c.id = d.id
ORDER BY d.start_time, c.chain_index;
-- Firefox visits
SELECT datetime(h.visit_date/1000000, 'unixepoch') AS visit_utc,
p.url, p.title, h.visit_type
FROM moz_historyvisits h JOIN moz_places p ON p.id = h.place_id
ORDER BY h.visit_date;
Retention
- Chrome: visits older than roughly 90 days are expired from
History;urlsrows can linger longer when referenced elsewhere. The user can clear history and downloads at any time. - Firefox: history is expired by volume, not a fixed age. Expiration starts when the number of pages exceeds a cap (
places.history.expiration.max_pages), and the most recent days of visits are excluded from expiration. - Both use SQLite WAL or journal files and leave deleted records in free pages until vacuumed.
Collection
Close the browser if you control the system, or copy the whole profile folder while it runs (Chrome holds a lock on History, so open a copy, never the original). Copy -wal, -shm and -journal companions.
ditto ~/Library/Application\ Support/Google/Chrome /cases/host01/Chrome
ditto ~/Library/Application\ Support/Firefox/Profiles /cases/host01/FirefoxProfiles
- UAC:
chrome.yaml,edge.yaml,brave.yamlandfirefox.yamlbrowser artifacts. - Aftermath: collects history, downloads, cookies and extensions for Arc, Brave, Chrome, Edge, Firefox and Safari (root plus Full Disk Access).
- mac_apt:
CHROMIUMplugin (Chrome, Edge, Opera, Vivaldi, Brave, Arc) andFIREFOXplugin, from images or live mounts.
Parsing
- Hindsight: Chromium-family parser producing a unified timeline from a profile folder, for example
python hindsight.py -i "/cases/host01/Chrome/Default" -o host01_chrome. - mac_apt:
CHROMIUMandFIREFOXplugins covering history, downloads, top sites and Firefox form history. sqlite3with the queries above for quick triage.
Investigator tips
- Enumerate every profile and every Chromium browser:
Profile 3of Brave is as relevant as ChromeDefault. - Chrome
downloads_url_chainsexposes the full redirect path to a payload, which often reveals the real delivery host behind a link shortener. - Match each download to the file's
com.apple.quarantineattribute and the quarantine events database; those survive a cleared browser download list. - Never mix epochs in one timeline without converting: Chrome's 1601 microseconds, Firefox's Unix microseconds and Safari's 2001 seconds all look like plain big integers.
- A profile with an empty
Historybut a recentPreferencesmodification time or many cache files suggests clearing or private use. - Use KnowledgeC app-focus records to show the browser was in the foreground during a visit window.