Recovering recent history from SQLite WAL files
2026-05-19 · 2 min
Chrome and Firefox both open their main databases in WAL mode
(PRAGMA journal_mode=WAL). Transactions append to <db>-wal first;
SQLite later checkpoints them into the main .db / .sqlite. If you
copy only the main file you can silently lose the most recent visits,
downloads and cookies — frequently exactly the activity an investigation
is about.
Why this is the highest-value sidecar
The WAL contains committed transactions. They are not partial, they are
not provisional, they are just not yet merged. Browsers checkpoint on
size thresholds (default 1 MB), at clean shutdown, and on explicit
PRAGMA wal_checkpoint. A running Chrome session can hold hours of
visits in the WAL.
A clean shutdown empties the WAL into the main file. A crash, an acquisition during use, or pulling power leaves the WAL populated.
What to do during acquisition
- Always collect the
-waland-shmsidecars alongside the main DB. - Keep them together and with matching base names. SQLite will only attach the WAL whose name matches the database.
- Do not let any tool open the database read-write before you copy. A
rogue
sqlite3 historythat opens read-write triggers a checkpoint and reshuffles the WAL.
Reading uncommitted-looking data
A WAL frame stream is a sequence of 24-byte frame headers plus full pages. Each frame names the page number it overwrites and the salt of its commit. Open the matched DB + WAL with a recent SQLite library and the merge is automatic. If you need to recover deleted rows (rows that existed in older WAL frames but were later overwritten), you have two options:
- WAL frame carving. Walk the WAL outside SQLite, dump every frame
for every page, and reconstruct the per-page history. Tools like
Sanderson's
waliteanand Sleuth Kit's SQLite plugins do this. - Page-level recovery from the freelist. Even checkpointed WAL frames may have written deleted-row data into unallocated cells. A raw page walker over the merged DB recovers the freelist content, which often holds the deleted history rows verbatim.
Practical pitfalls
- Firefox's
places.sqlite-waland Chrome'sHistory-walfollow the same rules. The mistake repeats across both. - If the user ran "Clear browsing history" recently, the rows are deleted but the underlying pages may still hold them until SQLite reuses the cells. WAL frame analysis sometimes recovers what looked permanently gone.
- Mounted forensic images preserve the sidecars. Live triage scripts often do not.