On a modern machine, the browser is not an application — it is the application. Email, banking, chat, work, shopping, and half the operating system's own UI now run inside it. That makes the browser the single richest source of evidence on most hosts: it records where a person went, what they typed, what they searched for before they even pressed Enter, what they downloaded, who they logged in as, and what web apps kept in local storage.
This guide runs from "what is a browser profile" all the way down to the byte-level stores — SQLite, LevelDB, SNSS (the Session Network Serialization Stream Chromium writes its open tabs to) and the HTTP cache — and covers every artifact worth pulling: how it works, why it exists, how the browser uses it, where it lives on disk, and what it proves. Along the way it sets against each other the two engines that power almost every browser on Earth: Chromium (Blink) and Gecko (Firefox).
What this page covers
- The two engines
- Storage substrates
- Timestamps
- Encryption & secrets
- Navigation and intent
- Identity and secrets
- Web-app storage
- Sessions and cache
- Network & tracking
- Chromium ↔ Gecko cross-reference
- Recovery & anti-forensics
- Search providers & nearby devices
- How Crow-Eye parses it
- Reading the activity dashboard
- Binary anatomy of every structure
- What Crow-Eye does not decode
- Every table Crow-Eye writes
The two engines: Chromium vs Gecko
Nearly every browser an examiner meets is one of two families. Chromium (the Blink engine) powers Chrome, Edge, Brave, Opera, Vivaldi, Yandex, and the browser hidden inside Electron apps like Slack, Discord and Teams. Gecko powers Mozilla Firefox and its forks (LibreWolf, Waterfox, Tor Browser). These two families cover 99% of the browsers in the wild.
The deepest difference for a forensic examiner is not how they render pages — it is how they store data. Chromium scatters evidence across many small, single-purpose files inside a profile folder (one file for history, another for cookies, another for logins, a folder of LevelDB for local storage). Firefox centralises: one places.sqlite holds history, downloads and bookmarks together, and a single storage/ tree holds all web-app data. Neither is "better" — but the layouts differ enough that both have to be known.
Chromium (Chrome / Edge / Brave)
Extra profiles are Profile 1, Profile 2… Edge/Brave swap only the vendor folder (Microsoft\Edge, BraveSoftware\Brave-Browser).
Gecko (Firefox)
The profile name is random; profiles.ini in the parent maps names to folders and can point outside Profiles\.
The same seven questions — where did they go, what did they save, who are they — answered by different files in each engine.
| Concern | Chromium (Blink) | Gecko (Firefox) |
|---|---|---|
| Profile root | User Data\Default | Profiles\<rand>.default-release |
| History / downloads | History (sqlite; downloads inside it) | places.sqlite (history+downloads+bookmarks) |
| Cookies | Network\Cookies (sqlite, encrypted values) | cookies.sqlite (plaintext values) |
| Saved logins | Login Data (sqlite, DPAPI/os_crypt) | logins.json + key4.db (NSS) |
| Form/autofill | Web Data (autofill, addresses, cards) | formhistory.sqlite |
| Web-app storage | LevelDB (Local Storage\leveldb, IndexedDB) | storage\default + webappsstore.sqlite |
| Session/tabs | SNSS (Sessions\Session_*, Tabs_*) | sessionstore.jsonlz4 (mozLz4) |
| HTTP cache | Simple or Blockfile (Cache\Cache_Data) | cache2 (cache2\entries) |
| Master key | Local State → os_crypt (DPAPI-wrapped) | key4.db (NSS, optional master password) |
| Timestamps | WebKit (µs since 1601) | PRTime (µs since 1970) |
Storage substrates: the five containers
Before opening a single artifact, the containers come first. Almost everything a browser stores lives in one of five formats. Recognising the container already settles how to read the file — and, crucially, how to recover what was "deleted" from it.
Relational database files
History, Cookies, Login Data, Web Data, Favicons, DIPS, Media History (Chromium); places / cookies / formhistory (Firefox). Ships with -wal, -journal, -shm sidecars that hold uncommitted and recently-deleted rows.
Key-value log + SST tables
Local Storage, IndexedDB, Service Worker cache, extension storage, GCM push. A .log write-ahead file plus .ldb tables (snappy-compressed). Deleted keys survive as tombstones in the log.
Human-readable config
Preferences, Secure Preferences, Bookmarks, Local State (Chromium); logins.json (Firefox). Plain text — settings, extension permissions, the DPAPI-wrapped master key, and anti-forensics tells like exit_type.
Session serialization
Chromium session & tab state is SNSS command records; Firefox is an LZ4-compressed JSON (sessionstore.jsonlz4). Both hold open tabs, tab order, navigation history and unsubmitted form text.
Cached response bodies
Chromium Simple Cache (one file per entry) or the older Blockfile (data_0..3 + external f_* bodies); Firefox cache2 (entries\<hash> with a trailing metadata block). Holds the URL, headers and the actual bytes served.
Why the sidecars matter. A SQLite database in WAL mode writes new and changed rows to a -wal file first and only folds them into the main .db at a checkpoint. A collection that takes only History and ignores History-wal misses the most recent browsing — and rows a user "deleted" seconds ago may still be sitting in the WAL. Always collect the sidecars with the database.
Timestamps: three different zeros
A timestamp is only as good as the epoch it counts from. Browsers use two different origins, and getting them wrong throws every event off by centuries. This is the single most common browser-forensics mistake.
WebKit shares the Windows FILETIME epoch (1601) but counts microseconds, not 100-ns ticks — multiply by 10 to reuse FILETIME math.
| Format | Unit | Convert to UTC |
|---|---|---|
| WebKit (Chromium) | µs since 1601 | epoch_1601 + (value / 1e6) seconds |
| PRTime (Firefox) | µs since 1970 | epoch_1970 + (value / 1e6) seconds |
| Unix seconds | s since 1970 | epoch_1970 + value seconds |
History carries two Chromium times worth separating: visit_time dates a single visit, while last_visit_time on the URL row is only the most recent one. The timeline has to plot the per-visit time, or a hundred visits collapse into one dot.
Encryption and secrets: why passwords aren't plaintext
Two acronyms run through this section. DPAPI is the Windows Data Protection API, the operating system service that encrypts a blob against the logged-on user's credentials so only that user on that machine can decrypt it. NSS is Network Security Services, the Mozilla cryptography library Firefox uses instead. Saved passwords and cookie values are encrypted at rest by one or the other. The key chain is what decides exactly what has to be preserved to decrypt them later — and why simply reading Login Data yields ciphertext, not passwords.
Chromium wraps every secret with a per-profile os_crypt key. That key is itself encrypted and stored in Local State. The encryption scheme is written as a prefix on each blob:
v10/v11— AES-256-GCM, where the key is protected by Windows DPAPI (tied to the user's login). Decryptable while running as that user.v20— App-Bound Encryption — the modern default in Chrome/Edge/Brave. The key is additionally bound to the browser via a SYSTEM-level elevation service, so it cannot be decrypted just by being the user. This deliberately breaks the classic "copy Login Data and decrypt" attack.dpapi— a bare DPAPI blob (older builds).plaintext— no encryption prefix.
Firefox does it differently: usernames and passwords live in logins.json as base64 blobs encrypted by NSS, and the key material is in key4.db (optionally protected by a master password). Decrypting Firefox logins later requires key4.db — so a sound acquisition copies it alongside logins.json.
Sound practice: a forensic parser should record the encrypted blob plus the encryption scheme and the wrapped master key, and decrypt in a separate, audited step — never write plaintext credentials into a case database as a side effect of parsing. Preserve, don't crack, at collection time.
Identity and secrets
Who is behind the keyboard? Cookies, saved logins, autofill, addresses and payment methods answer that — and they are exactly the artifacts protected by the encryption chain from Section 4.
Cookies and session tokens ChromiumGecko
| Engine | Path |
|---|---|
| Chromium | …\User Data\Default\Network\Cookies |
| Firefox | …\Profiles\<p>\cookies.sqlite |
Saved credentials ChromiumGecko
Login Data (sqlite); Firefox logins.json + key4.db.Autofill, addresses and payment methods ChromiumGecko
Web Data (autofill, addresses + address_type_tokens, credit_cards); Firefox formhistory.sqlite.Web-app storage
Modern sites are applications. WhatsApp Web, Slack, crypto wallets and password-manager extensions keep real state on disk — chat fragments, tokens, wallet vaults — in LevelDB and IndexedDB. This is where a lot of the richest, least-expected evidence hides.
Local storage and IndexedDB ChromiumGecko
webappsstore.sqlite and the storage/default/<origin> tree..log.Local Storage\leveldb, IndexedDB\ (Chromium); webappsstore.sqlite, storage\default (Firefox).Extensions and extension storage Chromium
Local Extension Settings\<id> and Extension State.Extensions\, Local Extension Settings\, Extension State\ in the profile.Service workers and CacheStorage Chromium
Service Worker\CacheStorage.…\Default\Service Worker\CacheStorage\Sessions and the HTTP cache
What was on screen when the browser last closed? And what did those pages actually contain? Session state and the disk cache answer both — including tabs a user closed and pages the live web no longer serves.
Active sessions and unsaved form inputs ChromiumGecko
Sessions\Session_* / Tabs_* (Chromium); sessionstore.jsonlz4 (Firefox).HTTP disk cache ChromiumGecko
data_0..3 + external f_* bodies); Firefox uses cache2 (per-entry file with a trailing metadata block).Cache\Cache_Data\ (Chromium); cache2\entries\ (Firefox, under %LOCALAPPDATA%).Network state and tracking
Beyond pages and logins, the browser keeps low-level network memory — which servers it has spoken to, which enforce HTTPS, and which sites tried to track a user across others. Niche, but decisive in the right case.
| Artifact | What it records | Forensic value |
|---|---|---|
| Network state C | Network Persistent State (alt-svc / HTTP-3 servers), TransportSecurity (HSTS hosts + expiry), Reporting and NEL endpoints. | Proof a host was contacted even if no page/cookie remains; HSTS entries persist per visited domain. |
| DIPS C | Bounce-tracking database: per-site first/last user-interaction and storage times. | A chronology of tracker contact and genuine interaction, site by site. |
| Push / GCM C | Background push-notification registration tokens between the browser and app servers. | Links the machine to specific web-app back ends. |
| Media history C | Playback origins, watch time, last-played times. | What media was played, where, and for how long. |
Chromium and Gecko cross-reference
The master lookup: one artifact, both engines' homes.
Table 4| Artifact | Chromium store / path | Firefox store / path |
|---|---|---|
| History & visits | History (urls, visits) | places.sqlite (moz_places, moz_historyvisits) |
| Downloads | History (downloads) | places.sqlite (moz_annos) |
| Bookmarks | Bookmarks (JSON) | places.sqlite (moz_bookmarks) |
| Cookies | Network\Cookies (encrypted) | cookies.sqlite (plaintext) |
| Saved logins | Login Data + Local State key | logins.json + key4.db |
| Form / autofill | Web Data | formhistory.sqlite |
| Local Storage | Local Storage\leveldb | webappsstore.sqlite / storage\default |
| IndexedDB | IndexedDB\ (LevelDB) | storage\default\<origin>\idb |
| Sessions | Sessions\ (SNSS) | sessionstore.jsonlz4 |
| HTTP cache | Cache\Cache_Data (Simple/Blockfile) | cache2\entries |
| Settings | Preferences (JSON) | prefs.js |
Recovery and anti-forensics
"I cleared my history" is rarely the end of the story. Because of how the substrates work, deleted browsing leaves echoes in several places at once.
- WAL / journal: rows deleted just before shutdown may still sit in
History-wal— collect the sidecars and read them. - Favicons: the icon database keeps a domain's icon after its History rows are gone — proof of a visit that "clearing history" missed.
- HTTP cache: cached page bodies outlive the History entry that referenced them.
- LevelDB tombstones: a deleted key is written as a deletion record in the
.log, not physically removed — recoverable until compaction. - DIPS & network state: record site contact independently of History.
- Anti-forensics tells:
Preferenceskeeps the lastexit_typeand clear-browsing-data timing — the act of clearing is itself an event.
Search providers and nearby devices
Two artifacts sit outside the usual history-and-cookies story, and both get skipped far too often. One decides where every address-bar search is sent; the other records hardware the browser found on the local network. Neither is a page visit, so neither shows up in a history review — and that is exactly why they survive one.
Search providers and keywords Chromium
keywords table inside Web Data holds every search provider the profile knows: a display name, a keyword trigger, the search URL template with a {searchTerms} placeholder, and usually a suggestion endpoint and favicon URL.is_default flag on its own. Chromium records the default by GUID elsewhere, and the parser can only resolve it when that pointer is present — on plenty of real profiles the flag is 0 for every row. Read the provider list itself: a rogue entry is visible whether or not it is marked default.| Engine | Path |
|---|---|
| Chrome | %LOCALAPPDATA%\Google\Chrome\User Data\Default\Web Data |
| Edge | %LOCALAPPDATA%\Microsoft\Edge\User Data\Default\Web Data |
| Brave | %LOCALAPPDATA%\BraveSoftware\Brave-Browser\User Data\Default\Web Data |
Media router: Cast and DIAL Chromium
media_router block inside Preferences. It holds a per-profile receiver_id_hash_token — a stable pseudonymous identifier the browser presents to receivers — and, when the browser has recorded them, entries for discovered devices.receiver_id_hash_token but no device list — so expect rows carrying the token with the device-detail columns empty. An empty device list is not a parsing failure; it means the browser had nothing recorded to hand over.| Engine | Path |
|---|---|
| Chrome | %LOCALAPPDATA%\Google\Chrome\User Data\Default\Preferences → media_router |
| Edge | %LOCALAPPDATA%\Microsoft\Edge\User Data\Default\Preferences → media_router |
| Brave | %LOCALAPPDATA%\BraveSoftware\Brave-Browser\User Data\Default\Preferences → media_router |
Why these two survive a clean-up
Clearing browsing data targets history, cookies and cache. It does not touch the search-provider
list in Web Data, and it does not reset the Cast token in Preferences.
On a host where the history looks suspiciously empty, both artifacts are still sitting there
— one naming services the user searched through, the other tying the profile to a stable
identifier and, sometimes, to physical hardware.
How Crow-Eye parses it
Everything above is engine behaviour, true of any tool. Here is how Crow-Eye turns it into evidence — the one place this guide talks about the product.
Cache bodies and IndexedDB blobs are extracted into the case folder with a hash-tracked inventory, and each cache entry is walked from its index so the real HTTP status and response headers come with it, and every row carries provenance — which browser, which user, which profile — so two users' Default\History never blur together. The result is a single, open-source, read-only view across Chromium, Gecko and Electron.
Reading the browser activity dashboard
Parsing writes 37 tables (all listed below). The Charts button on any browser tab
opens the same browser_analysis.db as an interactive dashboard, and two rules
hold everywhere in it: activity type is the colour, and the site is
the pivot. Those two settle how every chart on the page is read.
Each colour is one kind of browsing, unioned from its Chromium and its Gecko table, so a strip means the same thing whichever engine produced the row. Which browser a colour belongs to is never asked — the browser is a filter, not a colour.
Table 17| Colour | Chromium source | Gecko source | What the strip counts |
|---|---|---|---|
| Visits | browser_history | browser_gecko_history | Pages actually loaded |
| Searches & typed | browser_shortcuts | — | What was typed into the omnibox — intent, even for a site never opened |
| Downloads | browser_downloads | browser_gecko_downloads | What landed on disk, and whether the browser flagged it |
| Cookies set | browser_cookies | browser_gecko_cookies | Session tokens and trackers — these outlive a cleared history |
| Cache fetches | browser_cache, browser_service_worker | browser_cache | Resources served from disk, with their real HTTP status |
Why the site, and not the browser, is the pivot. Colour already answers
what kind of activity, so a machine with one browser reads exactly like a machine
with six. That frees the vertical axis for the question actually asked of browsing
evidence — which site. Every URL and every cookie host is folded to a bare
host first, so .example.com from a cookie and
https://example.com/page from a visit land on the same row, and one site's
visits, cookies, cache, downloads and saved login line up side by side.
The dashboard is two columns. The left column follows one day at a time; the right column stays on the whole selected range, so a single day can be read without losing the shape of the case.
Table 18| Region | What it shows | What to read from it |
|---|---|---|
| Five heat-maps across the top | One calendar per activity; a cell is a day, shaded by that activity's record volume, normalised within its own map | Whether the five kinds of activity rise and fall together. They share an axis, so a column is the same day in all five, and selecting a day selects it in all five at once |
| Proportion strip | The selected day's activity mix as one bar | The day's character at a glance — a day that is almost all cache is a day of background loading, not browsing |
| By hour | Stacked bars, one colour per activity, across 24 hours | Working hours against night activity. Click a bar to open that hour |
| Top domains | The day's busiest sites, each bar split by activity | Which sites the day was actually about, and in what way |
| Domain × hour | A bubble per site per hour; size is the number of events, colour the activity | Session shape — a burst of one site across an hour reads differently from a single visit |
| Domains on this day | Every busy site with its per-activity counts | Click any row for the full site profile |
| Events on this day | The individual rows: time, activity, site, page title or file, browser and profile | The evidence itself, oldest first, once the charts have shown where to look |
| Overview right column | Totals, Activity mix, Top domains split typed against followed, What was typed, Downloads, Anti-forensics signals and Records by activity | The whole range rather than one day — what this machine's browsing looks like overall |
Two filters sit in the header and apply to everything below them: a search box — several terms, matched ANY or ALL against URL, title, host and file name — and a date dropdown that narrows the range. Where a case holds more than one browser or more than one profile, a selector for each appears beside them.
No secret is ever drawn. The dashboard reads the same database as every
other view, and that database deliberately holds encrypted blobs rather than plaintext.
Cookie values, browser_autofill.value and password_encrypted_b64
stay where they are: a saved login appears as a login exists for this host, never
as a credential. A chart must not undo what the parser was careful not to do.
Binary anatomy: every structure Crow-Eye decodes
Everything above is behaviour. This is the wire format — every magic number and byte layout the parser actually reads. Pick a structure, then click any byte to see which field it belongs to, what it decodes to, and why it matters.
The bytes are real. Every map below was measured off a live Chromium, Edge or Firefox profile, and each structure was validated against its own magic number before a single field was trusted. A decoder that cannot confirm its magic returns nothing rather than a plausible guess, because a wrong offset in a forensics tool produces convincing wrong evidence — three of the offsets on this page were wrong at some point, and each one decoded to something that looked entirely reasonable.
Anywhere a byte could carry real browsing, it has been replaced with a placeholder of exactly the same length, so every offset shown stays true. Bytes that no field claims are greyed out and say so when clicked: undocumented space is a finding, not a gap.
All Chromium structures are little-endian; Firefox cache2 is big-endian. That single difference is the most common source of garbage values when people write their own parsers.
Select any byte in the map to see the field it belongs to.
Full dissection reference
The same fields as a table. Both are drawn from one definition, so the map and the table cannot disagree with each other.
Table 5| Offset | Size | Field | What it is |
|---|
The cache address, bit by bit
One structure on this page is not a run of bytes but a packed 32-bit word, so it gets a table rather than a map. Every entry in a blockfile cache is reached through one of these: it names the file, the block size and the block number in a single integer.
Table 6| Bits | Width | Field | What it is |
|---|---|---|---|
| 31 | 1 | Initialised | Zero means the address is unused. An address of 0x00000000 is not block zero — it is "nothing here", and treating it as a location is how a walk invents entries. |
| 30–28 | 3 | File type | 0 means the payload lives in a separate f_XXXXXX file. 1–4 select data_0–data_3, whose block sizes are 36 / 256 / 1024 / 4096 bytes. |
| 27–24 | 4 | Blocks − 1 | How many consecutive blocks the payload occupies, stored one less than the real count so four bits can express one to sixteen. |
| 23–16 | 8 | File selector | Which file of that type, for caches large enough to chain. |
| 15–0 | 16 | Block number | Index of the first block. The byte offset is 8192 + block_number × block_size — the 8192 is the block file header, and omitting it lands mid-record every time. |
Value-level encodings
Below the file formats sit the encodings that individual values use. None of these is a file layout, so none gets a map, but each one turns a correct byte read into a wrong answer when it is missed.
Table 7| Encoding | Literal | Where | What goes wrong without it |
|---|---|---|---|
| WebKit time | µs since 1601 | Chromium history, cookies, cache | Read as Unix seconds it lands in 1970; read as FILETIME it is off by a factor of ten. |
| PRTime | µs since 1970 | Firefox places, cookies, forms | Read as seconds, every Firefox timestamp lands tens of thousands of years out. |
| Unix, three scales | s / ms / µs | Chromium autofill, Firefox cookie expiry | Firefox moved moz_cookies.expiry to milliseconds; a seconds-only converter overflowed and silently produced an empty string. Crow-Eye picks the scale by magnitude. |
| IndexedDB key prefix | byte 0 | IndexedDB LevelDB keys | The first byte packs the byte-widths of the database, object-store and index ids. Assume fixed widths and every id after the first is misread. |
| Local Storage flag | 0x00 / 0x01 | Local Storage LevelDB values | 0x00 marks UTF-16LE, 0x01 UTF-8. Decode a UTF-16 value as UTF-8 and it comes out interleaved with NULs. |
| Chromium secret | v10 / v11 / v20 | Cookies, saved logins | The prefix names the scheme: v10/v11 are AES-GCM under a DPAPI-wrapped key, v20 is App-Bound. A raw DPAPI blob starts 01 00 00 00 instead. Crow-Eye classifies and preserves; it never decrypts. |
| Body sniffing | 1F 8B | Cache bodies | gzip 1F 8B, zstd 28 B5 2F FD, JPEG FF D8 FF. Used to label a body whose Content-Encoding header did not survive. |
What Crow-Eye does not decode, and why
A column that is empty proves nothing on its own. It could mean the artifact held nothing, or it could mean the tool did not look. Those are completely different findings, and a report that cannot separate them is not evidence. So this section states, for every column that comes back empty, which of the two it is.
Genuine limits of the parser
Table 8| What | Where | Why | What to do instead |
|---|---|---|---|
| Window and tab placement | browser_sessions | Placement comes from two commands that Session_* files carry and Tabs_* / Apps_* files often do not. Where they are absent the columns stay NULL rather than being guessed. | The navigation order within each tab is still intact, and the session file name distinguishes the streams. |
| Entries the index lost | cache_format = blockfile_scan | These are URL strings recovered from block data that the index hash table did not account for — an evicted or partly overwritten entry. There is no record to attach a status to. | Treat them as proof a URL appeared, not as a cache record. The blockfile rows beside them are the real entries. |
| Request time, Firefox | browser_cache where cache_format = firefox_cache2 | cache2 records lastFetched and lastModified, but nothing that means "when the request was sent". Chromium stores both times, Firefox does not. | response_time carries the fetch time. request_time is left empty rather than filled with lastModified, which means something else. |
| Compressed bodies | browser_cache, browser_service_worker | Bodies are preserved exactly as stored, not decompressed, so the extracted file is byte-for-byte what was on disk. | content_encoding names what would have to be undone — gzip, br or zstd. |
| Encrypted secrets | browser_cookies, browser_credentials | Deliberate. Crow-Eye classifies the scheme and preserves the blob; it never decrypts on the analyst's behalf. | The scheme label plus the key material in browser_metadata is everything a decryption step needs. |
Empty because the artifact was empty
These columns are decoded correctly and come back blank on most machines because the browser never stored anything in them. On a profile that does use the feature, they fill.
Table 9| Column | Source | When it fills |
|---|---|---|
bank_name, nickname | masked_credit_cards | Only for server-side cards the user has labelled. A locally saved card has neither. |
state, company | address_type_tokens | Only when the saved address itself carries those fields — many addresses are street, city and postcode alone. |
origin | browser_push | Only for a Web Push registration made by a site. A browser feature registration such as tab sharing has a product id, not an origin. |
response_time | browser_service_worker | CacheStorage records the response but not when it was fetched; the entry file's own timestamps are the nearest thing. The ordinary HTTP cache does carry fetch times — this gap is specific to CacheStorage. |
Every table Crow-Eye writes
Section 12 ended on "one case database". This is that database, table by table — all
37 of them, so nothing in this guide is left without an address in the
product. They live in Target_Artifacts\browser_analysis.db, and the same set is
produced for Chromium, Gecko and Electron targets alike, and for a live system, a collected
folder or a forensic image. On a collected folder or an image, user_name is the
Users folder the profile was found in and sid comes from that
evidence’s own SOFTWARE hive when the case holds a single source (otherwise it is
left empty).
Every table carries the same seven provenance columns —
browser, vendor, user_name, sid,
profile, source_path and parsed_at. That is what keeps
two users' Default\History from blurring together, and it is why the columns
below list only what is specific to each table.
Navigation and intent
Table 10| Table | One row is | Source on disk | Key columns | What it proves |
|---|---|---|---|---|
browser_history | One visit to one URL | History (urls + visits) | url, title, visit_time, transition, from_visit_url | Where the user went and how they got there — the transition separates a typed URL from a redirect. |
browser_downloads | One download | History (downloads + URL chain) | target_path, source_url, referrer, danger_type, state | What landed on disk, from where, and whether the browser flagged it. Survives deletion of the file itself. |
browser_shortcuts | One omnibox shortcut the browser learned | Shortcuts | text, fill_into_edit, url, number_of_hits | What the user actually typed, not merely visited — intent, even for sites never opened. |
browser_network_predictor | One learned omnibox prefix | Network Action Predictor | user_text, hit_count, miss_count | Partial typing the user abandoned — intent that never became a visit. |
browser_favicons | One page-to-icon mapping | Favicons | page_url, icon_url, icon_domain, last_updated | Corroborates a visit after history is cleared — favicons are frequently missed by wipers. |
browser_top_sites | One entry on the new-tab grid | Top Sites | url, title, url_rank | A ranked view of habitual destinations, derived independently of the history table. |
browser_reading_list | One saved-for-later page | Bookmarks (reading-list node) | title, url, date_added, read_status | Deliberate retention of a page, with whether it was ever opened. |
Identity and secrets
Table 11| Table | One row is | Source on disk | Key columns | What it proves |
|---|---|---|---|---|
browser_cookies | One cookie | Network\Cookies | host_key, name, creation_time, last_access_time, encrypted_value_b64 | Which sites held a session and when it was last used. Values stay encrypted — see the note below. |
browser_credentials | One saved login | Login Data | origin_url, username_value, password_encrypted_b64, times_used, blacklisted | Which accounts exist on which sites. blacklisted marks a site the user explicitly refused to save. |
browser_autofill | One remembered form field | Web Data | field_name, value, count, date_created, date_last_used | Text the user typed into forms — search terms, usernames, identifiers — with first and last use. |
browser_addresses | One saved address profile | Web Data | full_name, city, country, phone, email | Identity attribution: name, locality, phone and email bound to the profile. Street-level fields live in a separate Web Data table that is not read yet. |
browser_payments | One saved card or IBAN | Web Data | name_on_card, last_four, network, expiration_month, card_number_encrypted_b64 | Financial instruments tied to the user. Only the last four digits are ever in clear. |
browser_metadata | One profile's crypto & version record | Local State / Preferences; Gecko key4.db | os_crypt_key_b64, key_scheme, profile_created | The DPAPI-wrapped master key and its scheme — what a later, audited decrypt would need. |
Nothing above is written in plaintext. Cookie values, passwords
and card numbers are preserved as base64 of the still-encrypted blob, with
encryption_version recording the scheme (v10/v11,
App-Bound, or NSS). Crow-Eye stores the evidence; it does not silently crack it.
Web-app storage
Table 12| Table | One row is | Source on disk | Key columns | What it proves |
|---|---|---|---|---|
browser_local_storage | One key/value pair for one origin | Local Storage\leveldb | origin, key, value, is_deleted, seq | Application state a site kept on the host. is_deleted surfaces LevelDB tombstones — values the page thought it had erased. |
browser_indexeddb | One record in one object store | IndexedDB (Gecko: storage\default) | origin, database_name, object_store, key, value | The bulk store behind modern web apps — cached messages, mail and documents. The object store is recovered by decoding the IndexedDB key prefix, so records carry the store they belong to. Extracted blobs are inventoried in browser_files. |
browser_extension_storage | One key/value pair owned by an extension | Local / Sync / Managed Extension Settings, Extension State / Rules / Scripts | extension_id, store_kind, key, value, is_deleted | Where crypto-wallet vaults and extension password managers live — often the highest-value store on the host. |
browser_service_worker | One resource cached by a service worker | Service Worker\CacheStorage | scope, resource_url, request_method, http_status, content_type, content_encoding, server_headers, content_length | Content a site can serve with no network at all — survives an ordinary cache clear. Each CacheStorage bucket is a Simple Cache instance, keyed by the origin that registered the worker. The response metadata is a protobuf stored after the body, so the status and headers come from there rather than from an HTTP response head. |
Sessions and cache
Table 13| Table | One row is | Source on disk | Key columns | What it proves |
|---|---|---|---|---|
browser_sessions | One navigation entry from a saved session | Sessions\Session_* / Tabs_* / Apps_* (SNSS) | session_file, window_index, tab_index, url, title, form_text | What was open at the moment of capture — including form_text, text typed into a form and never submitted. Placement comes from the SetTabWindow and SetTabIndexInWindow commands, not from the navigation record, whose own index counts page loads within a tab. Streams that carry no placement command leave both columns NULL. |
browser_cache | One cache record, or a URL recovered from cache data | Cache\Cache_Data (Gecko: cache2\entries) | url, http_status, content_type, content_encoding, server_headers, content_length, cache_format | Page content as it was actually served, with the real response headers. cache_format says which of the two a row is: blockfile and firefox_cache2 rows are genuine cache entries walked from the index, carrying status and headers; blockfile_scan rows are URL strings recovered from block data that the index did not account for — evidence a URL appeared, but not a cache record. |
browser_media_history | One watched media item | Media History | origin, url, watch_time_seconds, position_seconds | Not just that a video page was opened, but how much of it was actually played. |
Configuration and inventory
Table 14| Table | One row is | Source on disk | Key columns | What it proves |
|---|---|---|---|---|
browser_bookmarks | One bookmark | Bookmarks (JSON) | folder, name, url, date_added, guid | Deliberate retention, with the folder path showing how the user organised it. |
browser_preferences | One setting | Preferences / Secure Preferences | setting_key, setting_value, category | Proxy, download directory, content permissions and sync state — configuration that shapes every other artifact. |
browser_extensions | One installed extension | Extensions\ + Secure Preferences | extension_id, name, version, permissions, install_time, from_webstore | What code ran inside the browser. from_webstore = false means side-loaded — a standard persistence route. |
browser_search_engines | One search provider | Web Data (keywords) | short_name, keyword, url, suggest_url, favicon_url | An injected provider is a classic hijack. Judge it by the list, not by is_default — that flag is frequently 0 for every row (§12). |
browser_files | One artifact copied out of the profile | (extraction inventory) | artifact, original_path, extracted_path, size, sha1, mtime | The chain of custody for everything pulled into the case folder — each file hashed where it came from. |
Tracking and network state
Table 15| Table | One row is | Source on disk | Key columns | What it proves |
|---|---|---|---|---|
browser_dips | One site tracked for bounce-tracking mitigation | DIPS | site, first_site_storage_time, last_user_interaction_time | Chromium's own record of sites that stored data — including ones the user never knowingly visited, and it outlives a history clear. |
browser_network_state | One host's network-layer record | Network Persistent State, TransportSecurity | source, host_or_key, detail, expiry | HSTS pins and broken-alternative-service entries prove contact with a host even when no page was cached. |
browser_push | One push-notification registration | GCM Store\Encryption (LevelDB) | app_id, registration_id, source_key | A site or extension registered to receive messages — a channel that keeps working with the page closed. The registration is recovered from the GCM LevelDB; origin and sender are encoded inside the key and are not split out yet. |
browser_media_router | One Cast entry — the per-profile receiver token, or a discovered device | Preferences → media_router | device_name, ip_endpoint (carries the token), device-detail columns when present | A stable per-profile identifier that links two captures to the same browser; device rows, when Chromium kept them, place the host on a LAN. See §12. |
Gecko-specific tables
Firefox stores the same evidence in different files, so it gets its own eight
tables rather than being force-fitted into the Chromium schema. browser_cache,
browser_indexeddb, browser_files and browser_metadata
are shared — Gecko rows land there too, tagged vendor = gecko.
| Table | One row is | Source on disk | Key columns | What it proves |
|---|---|---|---|---|
browser_gecko_history | One visit | places.sqlite | url, title, visit_time, visit_type, from_visit_url | The Firefox equivalent of browser_history, including the referring visit. |
browser_gecko_bookmarks | One bookmark | places.sqlite | folder, title, url, date_added, last_modified | Bookmarks live in the same database as history in Gecko — one file, three artifact classes. |
browser_gecko_downloads | One download | places.sqlite (annotations) | url, target_path, start_time, state | Often empty on modern builds — absence here is a statement about the build, not a parser failure. |
browser_gecko_cookies | One cookie | cookies.sqlite | host, name, value, creation_time, last_accessed | Unlike Chromium, Gecko cookie values are not encrypted at rest — session tokens are readable directly. |
browser_gecko_credentials | One saved login | logins.json + key4.db | hostname, username_encrypted_b64, password_encrypted_b64, times_used | NSS-encrypted; both username and password are protected, unlike Chromium's clear username. |
browser_gecko_formhistory | One remembered form value | formhistory.sqlite | field_name, value, times_used, first_used, last_used | Typed form and search input, with a first/last-use pair. |
browser_gecko_sessions | One tab entry | recovery.jsonlz4 / sessionstore.jsonlz4 | window_index, tab_index, url, title, form_text | Open tabs and unsubmitted form text, from mozLz4-compressed JSON. |
browser_gecko_localstorage | One key/value pair | webappsstore.sqlite | origin, key, value, source_kind | Gecko keeps local storage in SQLite rather than LevelDB — no tombstones, but no wear-levelled remnants either. |
Reading an empty table
Several tables can be legitimately empty on a given host — browser_reading_list,
browser_media_history and browser_gecko_downloads among them. On the
host this reference was checked against, no profile had a Media History database at
all, so that emptiness is a fact about the browser, not the parser. The table is created either
way, which is what separates "the feature was never used" from "this artifact was
never looked at" — but treat a surprising empty table as something to confirm at the
source, not to assume. browser_service_worker used to sit in this list and did not
belong there: CacheStorage is a Simple Cache, not a LevelDB, and once it is read as one the
table fills.
Pull every artifact in this guide with Crow-Eye
History, cookies, saved logins, cache, sessions, web-app storage and more — across Chrome, Edge, Brave, Firefox and Electron apps — collected and parsed into one timeline, for free.
Download Crow-Eye