browserforensics
Back to all articles

Chrome Local Storage & IndexedDB (LevelDB)

2026-05-19 · 2 min

Beyond the SQLite files, Chrome keeps web-app state in LevelDB key-value stores. Most investigators skip them and miss the richest source of intent evidence on the machine. A logged-in identity, a draft message, an OAuth token, the URL of a Drive document the user opened — that data lives in Local Storage or IndexedDB, not in History.

Where it lives in the profile

Local Storage/leveldb/             # window.localStorage per origin
Session Storage/                   # per-tab session storage
IndexedDB/<origin>.indexeddb.leveldb/    # structured app databases
IndexedDB/<origin>.indexeddb.blob/        # large blobs referenced by the LDB

Each entry is a folder, not a single file: CURRENT, MANIFEST-NNNNNN, one or more *.ldb SSTables, and a *.log write-ahead log. Copy the whole directory or LevelDB will refuse to open it.

The format you actually have to read

LevelDB is an LSM tree. To enumerate keys correctly:

  1. Read CURRENT to find the active manifest.
  2. Walk MANIFEST-* to learn which SSTables are live at which level.
  3. Merge the live SSTables with the *.log WAL, newest write wins per key.
  4. For each block, decompress Snappy if the trailer says so.

The values then need format-specific decoding:

  • Local Storage keys are _<origin>\x00<key>. Values start with a one-byte encoding marker: 0x00 UTF-16LE, 0x01 Latin-1, sometimes preceded by a meta byte.
  • Session Storage uses a namespace -> map -> key indirection. Resolve via the meta-namespace keys before extracting values.
  • IndexedDB values are V8 structured-clone encoded. Numbers, strings and simple objects round-trip cleanly; cyclic refs and typed arrays need careful handling.

Real evidence you find here

  • Slack, Teams, Discord, Notion: logged-in user IDs, workspace IDs, recently-opened channels, draft messages.
  • Google Docs / Drive: file IDs (the long random part of a Drive URL), recently-edited document titles, sync state.
  • VS Code Web / GitHub Codespaces: open file paths, repo names.
  • Banking and SSO: JWT tokens with claims still parseable (audience, issuer, expiry, sometimes username).
  • Crypto wallets: stored addresses, often the wallet identifier.

Caveats

  • The WAL (*.log) is where the most recent writes live. If you copy only the *.ldb files, you lose the last minutes of activity.
  • LevelDB compaction can produce tombstones for deleted keys that still surface during merge. Treat unexplained NULs in your output as deletes, not data.
  • Browsers occasionally migrate origins between LevelDB stores. A clean shutdown is friendlier; a crash mid-migration can leave orphaned data worth recovering by hand.

Further reading