SQLite forensics: why deleting rows doesn't erase secrets (FTS, free pages, VACUUM)
You deleted the row. The secret is gone from the app, the queries return nothing, and the dashboard is clean. In SQLite — the database behind most session stores, browser profiles, and agent state files — that delete is a fiction. The bytes are still in the file. Three ways deleted data survives 1. Free pages. SQLite doesn't zero out the space a deleted row occupied. The page is marked free and added to the freelist; the old bytes stay until they're overwritten by a future write. A file that's been deleted-from is a forensics goldmine: recover the freelist pages and the "deleted" rows come back. 2. FTS virtual tables. If the database uses SQLite's full-text search (FTS5), the FTS index keeps its own copies of the indexed text, maintained separately from the source tables. Delete the row from the source table and the FTS index still contains the tokens — searchable. This is the one that catches people: their app shows the secret is gone, and the FTS index still has it. 3. WAL and journal files. In WAL mode, recent writes live in the -wal file; transactions in the -journal file. Both can retain pre-delete content until checkpointed or cleaned. "Deleted" in SQLite means "no longer referenced", not "no longer present". What erasure actually requires Making a secret physically disappear from a SQLite database takes three operations, in order: Replace the value everywhere it lives. Known secret values get replaced across all tables; pattern matches (API key formats) get masked. Two layers, because you can't enumerate every secret that leaked. Rebuild the FTS indexes. INSERT INTO t(t) VALUES('rebuild') style rebuilds, or drop/recreate the virtual tables — so the index no longer contains the old tokens. Run VACUUM. VACUUM rewrites the entire database file, copying only live data into a fresh file — free pages with old bytes are discarded in the process. After VACUUM, the file's raw bytes no longer contain the secret. (Note: VACUUM doesn't shrink WAL files; those need a chec