Eye Describe Anatomy

Browser forensics, end to end

Every artifact a browser leaves behind — how it works, where it lives, and what it proves

Scroll to investigate

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).

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.

Profile layout — the same evidence, two shapes

Chromium (Chrome / Edge / Brave)

%LOCALAPPDATA%\Google\Chrome\User Data\
  ├─ Local State (master key)
  └─ Default\ (a profile)
     ├─ History (sqlite)
     ├─ Network\Cookies
     ├─ Login Data · Web Data
     ├─ Bookmarks · Preferences (json)
     ├─ Local Storage\leveldb\
     ├─ IndexedDB\ · Sessions\
     └─ Cache\Cache_Data\

Extra profiles are Profile 1, Profile 2… Edge/Brave swap only the vendor folder (Microsoft\Edge, BraveSoftware\Brave-Browser).

Gecko (Firefox)

%APPDATA%\Mozilla\Firefox\Profiles\
  └─ xxxx.default-release\
     ├─ places.sqlite (history+dl+bkmk)
     ├─ cookies.sqlite
     ├─ formhistory.sqlite
     ├─ logins.json + key4.db
     ├─ sessionstore.jsonlz4
     ├─ webappsstore.sqlite
     └─ storage\default\ (LS+IDB)
%LOCALAPPDATA%\…\cache2\entries\

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.

Table 1
ConcernChromium (Blink)Gecko (Firefox)
Profile rootUser Data\DefaultProfiles\<rand>.default-release
History / downloadsHistory (sqlite; downloads inside it)places.sqlite (history+downloads+bookmarks)
CookiesNetwork\Cookies (sqlite, encrypted values)cookies.sqlite (plaintext values)
Saved loginsLogin Data (sqlite, DPAPI/os_crypt)logins.json + key4.db (NSS)
Form/autofillWeb Data (autofill, addresses, cards)formhistory.sqlite
Web-app storageLevelDB (Local Storage\leveldb, IndexedDB)storage\default + webappsstore.sqlite
Session/tabsSNSS (Sessions\Session_*, Tabs_*)sessionstore.jsonlz4 (mozLz4)
HTTP cacheSimple or Blockfile (Cache\Cache_Data)cache2 (cache2\entries)
Master keyLocal State → os_crypt (DPAPI-wrapped)key4.db (NSS, optional master password)
TimestampsWebKit (µ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.

The five substrates and what lives in each
SQLite

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.

LevelDB

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.

JSON

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.

SNSS / mozLz4

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.

HTTP cache

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.

Where each clock starts counting
WebKit
microseconds since 1601-01-01 · Chromium (History, Cookies, Login Data, Downloads)
PRTime
microseconds since 1970-01-01 · Firefox (places, cookies, formhistory)
Unix
seconds since 1970-01-01 · autofill dates, some cookie expiries

WebKit shares the Windows FILETIME epoch (1601) but counts microseconds, not 100-ns ticks — multiply by 10 to reuse FILETIME math.

Table 2
FormatUnitConvert to UTC
WebKit (Chromium)µs since 1601epoch_1601 + (value / 1e6) seconds
PRTime (Firefox)µs since 1970epoch_1970 + (value / 1e6) seconds
Unix secondss since 1970epoch_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:

The Chromium key-wrapping chain
UserWindows loginSeeds the DPAPI master key
Local Stateos_crypt keyWrapped with DPAPI (v10) or app-bound (v20)
v20 onlyElevation serviceSYSTEM-level unwrap; blocks user-only decryption
ValueAES-GCM blobThe cookie / password ciphertext in the SQLite row

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

How it worksPer-site name/value pairs with host, path, creation/expiry/last-access times, and secure/httponly/samesite flags. Chromium encrypts the value (see §4); Firefox stores it in the clear.
Why it existsHTTP is stateless; cookies carry the "logged-in" state and site preferences between requests.
How the browser uses itSends matching cookies on every request to the site until they expire.
What it meansA live session token can prove an account was authenticated on this machine; creation times date the first login; the cookie file is usually locked while the browser runs.
EnginePath
Chromium…\User Data\Default\Network\Cookies
Firefox…\Profiles\<p>\cookies.sqlite

Saved credentials ChromiumGecko

How it worksOrigin URL, username (clear), and an encrypted password blob, plus created/last-used times and a use counter.
Why it existsThe built-in password manager, to auto-fill logins.
What it meansThe username alone attributes an account to the machine; times show when the login was first saved and last used. The password needs the key chain (§4) to decrypt.
WhereChromium Login Data (sqlite); Firefox logins.json + key4.db.

Autofill, addresses and payment methods ChromiumGecko

How it worksForm field history (name → value → count), saved postal addresses, and payment cards (name on card, last four, network, expiry — the card number kept as an encrypted blob).
Why it existsOne-click form filling at checkout and sign-up.
What it meansReal identity data — names, emails, phone numbers, addresses — plus every value ever typed into a form field, with first/last-used dates.
WhereChromium Web Data (autofill, addresses + address_type_tokens, credit_cards); Firefox formhistory.sqlite.
A sound parser stores card metadata and the encrypted number, never the plaintext PAN.

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

How it worksPer-origin key/value (Local Storage) and a full transactional object store (IndexedDB). Chromium keeps both as LevelDB; Firefox uses webappsstore.sqlite and the storage/default/<origin> tree.
Why it existsTo let web apps work offline and remember state without a server round-trip.
What it meansAuth tokens, cached messages, draft content, app configuration — keyed by the exact origin that wrote them. Deleted keys often survive as LevelDB tombstones in the .log.
WhereLocal Storage\leveldb, IndexedDB\ (Chromium); webappsstore.sqlite, storage\default (Firefox).

Extensions and extension storage Chromium

How it worksInstalled extensions (manifest, permissions) plus each extension's private LevelDB store under Local Extension Settings\<id> and Extension State.
Why it existsExtensions need somewhere isolated to persist their own data.
What it meansThis is where crypto-wallet vaults, password-manager blobs, and extension OAuth/access tokens live — extremely high-value, and frequently overlooked.
WhereExtensions\, Local Extension Settings\, Extension State\ in the profile.

Service workers and CacheStorage Chromium

How it worksProgressive Web Apps register service workers that cache API responses and assets for offline use, stored under Service Worker\CacheStorage.
Why it existsMakes web apps load and work with no network.
What it meansOffline copies of API payloads and pages — content that may no longer exist on the live server.
Where…\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

How it worksChromium serializes windows, tabs, tab order, per-tab navigation and page state as SNSS command records; Firefox writes an LZ4-compressed JSON snapshot.
Why it existsTo restore the user's tabs after a crash or restart.
What it meansReconstructs the exact set of open tabs and their order at last shutdown — and can hold text typed into forms but never submitted.
WhereSessions\Session_* / Tabs_* (Chromium); sessionstore.jsonlz4 (Firefox).

HTTP disk cache ChromiumGecko

How it worksCached responses store the request URL, HTTP status and headers, and the body bytes. Chromium uses Simple Cache (one file per entry) or the older Blockfile (data_0..3 + external f_* bodies); Firefox uses cache2 (per-entry file with a trailing metadata block).
Why it existsTo avoid re-downloading assets — speed and bandwidth.
What it meansA snapshot of what the user actually saw — images, scripts, JSON, whole pages — frozen at fetch time, often after the live site has changed or gone.
WhereCache\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.

Table 3
ArtifactWhat it recordsForensic value
Network state CNetwork 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 CBounce-tracking database: per-site first/last user-interaction and storage times.A chronology of tracker contact and genuine interaction, site by site.
Push / GCM CBackground push-notification registration tokens between the browser and app servers.Links the machine to specific web-app back ends.
Media history CPlayback 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
ArtifactChromium store / pathFirefox store / path
History & visitsHistory (urls, visits)places.sqlite (moz_places, moz_historyvisits)
DownloadsHistory (downloads)places.sqlite (moz_annos)
BookmarksBookmarks (JSON)places.sqlite (moz_bookmarks)
CookiesNetwork\Cookies (encrypted)cookies.sqlite (plaintext)
Saved loginsLogin Data + Local State keylogins.json + key4.db
Form / autofillWeb Dataformhistory.sqlite
Local StorageLocal Storage\leveldbwebappsstore.sqlite / storage\default
IndexedDBIndexedDB\ (LevelDB)storage\default\<origin>\idb
SessionsSessions\ (SNSS)sessionstore.jsonlz4
HTTP cacheCache\Cache_Data (Simple/Blockfile)cache2\entries
SettingsPreferences (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.

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

How it worksA 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.
Why it existsThe omnibox is a search box as well as an address bar. The browser needs a provider list so it knows where to send a query, and which provider to use by default.
How the browser uses itTyping a keyword then a space switches provider for that one query; everything else goes to the default. Sites in regular use can silently add themselves to this list via OpenSearch.
What it meansThis list is a durable record of the services a user searched through — including ones never bookmarked and long since removed from history. An entry pointing at an unfamiliar domain is a search hijack: every omnibox query was being routed through it.
Do not trust an 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.
EnginePath
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

How it worksChromium's Cast stack keeps its state in a 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.
Why it existsSo "Cast this tab" can find screens again without re-pairing, and so a receiver can recognise the same browser across sessions without a login.
How the browser uses itDiscovery runs over the local network (mDNS/DIAL). The browser only learns about devices that share a network segment with the host — it cannot see across the internet.
What it meansThis is network-proximity evidence from a browser artifact. A device entry says the machine was on the same LAN as that hardware. The receiver token is stronger in a different way: it is stable per profile, so the same token appearing in two captures ties them to the same browser profile even if everything else was wiped.
In practice the token is the reliable part. On many profiles Chromium keeps the 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.
EnginePath
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.

The browser parsing pipeline
1 · DiscoverEvery user & profileAll users on the host, or every Users folder collected from a folder or an image; every browser profile, plus Electron apps — adaptively, not one hard-coded path.
2 · CopyLocked files + sidecarsLive: a backup-semantics copy of locked databases together with their -wal / -journal / -shm. Folders and images: the collected tree, sidecars included.
3 · ReadRead-only, per substrateSQLite, LevelDB (incl. tombstones), JSON, SNSS and cache parsers — evidence never modified.
4 · PreserveMetadata-only secretsEncrypted blobs + the wrapped master key kept for a later audited decrypt; no plaintext written.
5 · OutputOne case databaseEvery row tagged with browser / user / profile provenance, feeding the Timeline, Search and Eye AI.

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
ColourChromium sourceGecko sourceWhat the strip counts
Visitsbrowser_historybrowser_gecko_historyPages actually loaded
Searches & typedbrowser_shortcuts—What was typed into the omnibox — intent, even for a site never opened
Downloadsbrowser_downloadsbrowser_gecko_downloadsWhat landed on disk, and whether the browser flagged it
Cookies setbrowser_cookiesbrowser_gecko_cookiesSession tokens and trackers — these outlive a cleared history
Cache fetchesbrowser_cache, browser_service_workerbrowser_cacheResources 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
RegionWhat it showsWhat to read from it
Five heat-maps across the topOne calendar per activity; a cell is a day, shaded by that activity's record volume, normalised within its own mapWhether 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 stripThe selected day's activity mix as one barThe day's character at a glance — a day that is almost all cache is a day of background loading, not browsing
By hourStacked bars, one colour per activity, across 24 hoursWorking hours against night activity. Click a bar to open that hour
Top domainsThe day's busiest sites, each bar split by activityWhich sites the day was actually about, and in what way
Domain × hourA bubble per site per hour; size is the number of events, colour the activitySession shape — a burst of one site across an hour reads differently from a single visit
Domains on this dayEvery busy site with its per-activity countsClick any row for the full site profile
Events on this dayThe individual rows: time, activity, site, page title or file, browser and profileThe evidence itself, oldest first, once the charts have shown where to look
Overview right columnTotals, Activity mix, Top domains split typed against followed, What was typed, Downloads, Anti-forensics signals and Records by activityThe whole range rather than one day — what this machine's browsing looks like overall
What each click opens
A dayThe day sectionSelects that day in all five heat-maps and rebuilds every chart beneath them for it.
An hour barThat hour, in detailThe hour's split by activity, and every site active in it with its browser and per-activity counts.
A siteThe full site profileEvery table joined for one host: visits, what was typed to reach it, cookies it set, what it served from cache, what was downloaded from it, and whether a login was saved for it.

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.

Figure 1
scroll the map sideways — it is sixteen bytes wide
Live byte selection

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
OffsetSizeFieldWhat 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
BitsWidthFieldWhat it is
311InitialisedZero 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–283File type0 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–244Blocks − 1How many consecutive blocks the payload occupies, stored one less than the real count so four bits can express one to sixteen.
23–168File selectorWhich file of that type, for caches large enough to chain.
15–016Block numberIndex 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
EncodingLiteralWhereWhat goes wrong without it
WebKit timeµs since 1601Chromium history, cookies, cacheRead as Unix seconds it lands in 1970; read as FILETIME it is off by a factor of ten.
PRTimeµs since 1970Firefox places, cookies, formsRead as seconds, every Firefox timestamp lands tens of thousands of years out.
Unix, three scaless / ms / µsChromium autofill, Firefox cookie expiryFirefox 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 prefixbyte 0IndexedDB LevelDB keysThe 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 flag0x00 / 0x01Local Storage LevelDB values0x00 marks UTF-16LE, 0x01 UTF-8. Decode a UTF-16 value as UTF-8 and it comes out interleaved with NULs.
Chromium secretv10 / v11 / v20Cookies, saved loginsThe 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 sniffing1F 8BCache bodiesgzip 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
WhatWhereWhyWhat to do instead
Window and tab placementbrowser_sessionsPlacement 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 lostcache_format = blockfile_scanThese 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, Firefoxbrowser_cache where cache_format = firefox_cache2cache2 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 bodiesbrowser_cache, browser_service_workerBodies 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 secretsbrowser_cookies, browser_credentialsDeliberate. 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
ColumnSourceWhen it fills
bank_name, nicknamemasked_credit_cardsOnly for server-side cards the user has labelled. A locally saved card has neither.
state, companyaddress_type_tokensOnly when the saved address itself carries those fields — many addresses are street, city and postcode alone.
originbrowser_pushOnly 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_timebrowser_service_workerCacheStorage 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.
The rule behind both tables. Every decoder on this page fails closed. If a magic number does not match, a length runs past the end of the buffer, or a status code falls outside 100–599, the decoder returns nothing rather than its best guess. An empty cell in a Crow-Eye output is always either "the artifact held nothing" or "this is listed above" — never "the parser produced something and nobody checked".

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
TableOne row isSource on diskKey columnsWhat it proves
browser_historyOne visit to one URLHistory (urls + visits)url, title, visit_time, transition, from_visit_urlWhere the user went and how they got there — the transition separates a typed URL from a redirect.
browser_downloadsOne downloadHistory (downloads + URL chain)target_path, source_url, referrer, danger_type, stateWhat landed on disk, from where, and whether the browser flagged it. Survives deletion of the file itself.
browser_shortcutsOne omnibox shortcut the browser learnedShortcutstext, fill_into_edit, url, number_of_hitsWhat the user actually typed, not merely visited — intent, even for sites never opened.
browser_network_predictorOne learned omnibox prefixNetwork Action Predictoruser_text, hit_count, miss_countPartial typing the user abandoned — intent that never became a visit.
browser_faviconsOne page-to-icon mappingFaviconspage_url, icon_url, icon_domain, last_updatedCorroborates a visit after history is cleared — favicons are frequently missed by wipers.
browser_top_sitesOne entry on the new-tab gridTop Sitesurl, title, url_rankA ranked view of habitual destinations, derived independently of the history table.
browser_reading_listOne saved-for-later pageBookmarks (reading-list node)title, url, date_added, read_statusDeliberate retention of a page, with whether it was ever opened.

Identity and secrets

Table 11
TableOne row isSource on diskKey columnsWhat it proves
browser_cookiesOne cookieNetwork\Cookieshost_key, name, creation_time, last_access_time, encrypted_value_b64Which sites held a session and when it was last used. Values stay encrypted — see the note below.
browser_credentialsOne saved loginLogin Dataorigin_url, username_value, password_encrypted_b64, times_used, blacklistedWhich accounts exist on which sites. blacklisted marks a site the user explicitly refused to save.
browser_autofillOne remembered form fieldWeb Datafield_name, value, count, date_created, date_last_usedText the user typed into forms — search terms, usernames, identifiers — with first and last use.
browser_addressesOne saved address profileWeb Datafull_name, city, country, phone, emailIdentity 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_paymentsOne saved card or IBANWeb Dataname_on_card, last_four, network, expiration_month, card_number_encrypted_b64Financial instruments tied to the user. Only the last four digits are ever in clear.
browser_metadataOne profile's crypto & version recordLocal State / Preferences; Gecko key4.dbos_crypt_key_b64, key_scheme, profile_createdThe 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
TableOne row isSource on diskKey columnsWhat it proves
browser_local_storageOne key/value pair for one originLocal Storage\leveldborigin, key, value, is_deleted, seqApplication state a site kept on the host. is_deleted surfaces LevelDB tombstones — values the page thought it had erased.
browser_indexeddbOne record in one object storeIndexedDB (Gecko: storage\default)origin, database_name, object_store, key, valueThe 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_storageOne key/value pair owned by an extensionLocal / Sync / Managed Extension Settings, Extension State / Rules / Scriptsextension_id, store_kind, key, value, is_deletedWhere crypto-wallet vaults and extension password managers live — often the highest-value store on the host.
browser_service_workerOne resource cached by a service workerService Worker\CacheStoragescope, resource_url, request_method, http_status, content_type, content_encoding, server_headers, content_lengthContent 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
TableOne row isSource on diskKey columnsWhat it proves
browser_sessionsOne navigation entry from a saved sessionSessions\Session_* / Tabs_* / Apps_* (SNSS)session_file, window_index, tab_index, url, title, form_textWhat 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_cacheOne cache record, or a URL recovered from cache dataCache\Cache_Data (Gecko: cache2\entries)url, http_status, content_type, content_encoding, server_headers, content_length, cache_formatPage 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_historyOne watched media itemMedia Historyorigin, url, watch_time_seconds, position_secondsNot just that a video page was opened, but how much of it was actually played.

Configuration and inventory

Table 14
TableOne row isSource on diskKey columnsWhat it proves
browser_bookmarksOne bookmarkBookmarks (JSON)folder, name, url, date_added, guidDeliberate retention, with the folder path showing how the user organised it.
browser_preferencesOne settingPreferences / Secure Preferencessetting_key, setting_value, categoryProxy, download directory, content permissions and sync state — configuration that shapes every other artifact.
browser_extensionsOne installed extensionExtensions\ + Secure Preferencesextension_id, name, version, permissions, install_time, from_webstoreWhat code ran inside the browser. from_webstore = false means side-loaded — a standard persistence route.
browser_search_enginesOne search providerWeb Data (keywords)short_name, keyword, url, suggest_url, favicon_urlAn 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_filesOne artifact copied out of the profile(extraction inventory)artifact, original_path, extracted_path, size, sha1, mtimeThe chain of custody for everything pulled into the case folder — each file hashed where it came from.

Tracking and network state

Table 15
TableOne row isSource on diskKey columnsWhat it proves
browser_dipsOne site tracked for bounce-tracking mitigationDIPSsite, first_site_storage_time, last_user_interaction_timeChromium's own record of sites that stored data — including ones the user never knowingly visited, and it outlives a history clear.
browser_network_stateOne host's network-layer recordNetwork Persistent State, TransportSecuritysource, host_or_key, detail, expiryHSTS pins and broken-alternative-service entries prove contact with a host even when no page was cached.
browser_pushOne push-notification registrationGCM Store\Encryption (LevelDB)app_id, registration_id, source_keyA 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_routerOne Cast entry — the per-profile receiver token, or a discovered devicePreferences → media_routerdevice_name, ip_endpoint (carries the token), device-detail columns when presentA 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 16
TableOne row isSource on diskKey columnsWhat it proves
browser_gecko_historyOne visitplaces.sqliteurl, title, visit_time, visit_type, from_visit_urlThe Firefox equivalent of browser_history, including the referring visit.
browser_gecko_bookmarksOne bookmarkplaces.sqlitefolder, title, url, date_added, last_modifiedBookmarks live in the same database as history in Gecko — one file, three artifact classes.
browser_gecko_downloadsOne downloadplaces.sqlite (annotations)url, target_path, start_time, stateOften empty on modern builds — absence here is a statement about the build, not a parser failure.
browser_gecko_cookiesOne cookiecookies.sqlitehost, name, value, creation_time, last_accessedUnlike Chromium, Gecko cookie values are not encrypted at rest — session tokens are readable directly.
browser_gecko_credentialsOne saved loginlogins.json + key4.dbhostname, username_encrypted_b64, password_encrypted_b64, times_usedNSS-encrypted; both username and password are protected, unlike Chromium's clear username.
browser_gecko_formhistoryOne remembered form valueformhistory.sqlitefield_name, value, times_used, first_used, last_usedTyped form and search input, with a first/last-use pair.
browser_gecko_sessionsOne tab entryrecovery.jsonlz4 / sessionstore.jsonlz4window_index, tab_index, url, title, form_textOpen tabs and unsubmitted form text, from mozLz4-compressed JSON.
browser_gecko_localstorageOne key/value pairwebappsstore.sqliteorigin, key, value, source_kindGecko 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.

From reading to doing

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