Cheat sheet — Browser & Application Artifacts¶
Companion to Module 05 — Browser & Application Artifacts · CC BY 4.0 — print it, pin it, share it.
Last reviewed: 2026-07
hindsight — parse a Chrome/Chromium profile¶
hindsight.py -i "Default/" -o report # -i profile dir, -o output basename
hindsight.py -i "Default/" -o report -f jsonl # JSONL (feeds the Module 07 super-timeline)
hindsight.py -i "Default/" -o report -f xlsx # spreadsheet for review
hindsight_gui.py # local web UI, then browse to it
hindsightreads the main DB and the WAL — that's where rows survive a "clear history." It converts Chrome's epoch for you.
Chrome profile — where the databases live¶
Linux ~/.config/google-chrome/Default/
Windows %LOCALAPPDATA%\Google\Chrome\User Data\Default\
macOS ~/Library/Application Support/Google/Chrome/Default/
History (SQLite: urls, visits, downloads, keyword_search_terms)
History-wal (Write-Ahead Log — often holds pre-clear rows)
Cookies, Login Data, Web Data (autofill), Network/Cookies
sqlite3 — query the History DB directly¶
sqlite3 History ".tables" # list tables
sqlite3 History ".schema urls" # column names + types
sqlite3 -header -csv History "SELECT ..." > out.csv
- Work on a copy. Opening the live DB can trigger a checkpoint and merge/prune the WAL —
copy
HistoryandHistory-waltogether, then query the copy.
SQL — the queries you always re-type¶
-- Visited URLs, newest first, with human time (Chrome epoch → local)
SELECT datetime(last_visit_time/1000000 - 11644473600,'unixepoch') AS visited,
url, title, visit_count
FROM urls ORDER BY last_visit_time DESC;
-- Downloads: where from, to where, how big
SELECT datetime(start_time/1000000 - 11644473600,'unixepoch') AS started,
tab_url, target_path, total_bytes
FROM downloads;
-- Search terms typed in the omnibox
SELECT term, url_id FROM keyword_search_terms;
-- Individual visits with referrer chain + transition type
SELECT datetime(visit_time/1000000 - 11644473600,'unixepoch') AS t,
url_id, from_visit, transition
FROM visits ORDER BY visit_time;
Epoch conversion (the 369-year trap)¶
Chrome / WebKit = microseconds since 1601-01-01 UTC (Windows FILETIME base)
SQL: datetime(field/1000000 - 11644473600, 'unixepoch')
Unix (Firefox places.sqlite uses microseconds since 1970):
SQL: datetime(field/1000000, 'unixepoch')
# Sanity-check one value on the CLI (Chrome epoch)
python3 -c "import datetime;print(datetime.datetime(1601,1,1)+datetime.timedelta(microseconds=13350000000000000))"
Gotchas worth remembering¶
- Chrome epoch is microseconds since 1601, not 1970. Forget
- 11644473600and every timestamp shifts ~369 years. Always sanity-check one known event before trusting a conversion — this mistake has shipped in real reports. - "Cleared history" ≠ gone. The WAL, OS DNS cache, download directory,
$MFT, prefetch, and network flows are all redundant records. CopyHistory-walalongsideHistory. - Incognito writes no
urls/visitsrows — but DNS cache and network flows still place the user on the site. Absence in the history DB is not proof of absence of activity. - Query a copy, never the live profile. Touching the live DB can checkpoint and prune the very WAL rows you came for.
- Same pattern beyond the browser: Slack (LevelDB), Teams (
%APPDATA%\Microsoft\Teams), Outlook (.ost) — all parseable offline. "I never downloaded it" is a testable claim.
Comments
Sign in with GitHub to comment. Choose the type: Feedback (errors or suggestions on this page) · Hints (help for fellow learners — no spoilers) · General (anything else).