Chrome
Chrome History
Overview
Chrome stores browsing history, downloads, and search terms in a single SQLite database named History within each profile directory. This database records every URL visited, the time and manner of each visit, files downloaded from the internet, and search queries performed via the omnibox.
The History database is one of the most forensically valuable Chrome artifacts, providing a detailed timeline of user web activity with visit-level granularity. Unlike some browsers that only record the most recent visit to each URL, Chrome maintains a separate record for every individual visit, enabling precise reconstruction of browsing patterns.
File Locations
| Artifact | Path |
|---|---|
| History database | ~/Library/Application Support/Google/Chrome/{Profile}/History |
| Journal file | ~/Library/Application Support/Google/Chrome/{Profile}/History-journal |
| WAL file | ~/Library/Application Support/Google/Chrome/{Profile}/History-wal |
| SHM file | ~/Library/Application Support/Google/Chrome/{Profile}/History-shm |
Replace {Profile} with the profile directory name (e.g., Default, Profile 1). The same structure applies to Chrome Canary and Chromium at their respective base paths.
Database Schema
urls Table
The urls table contains one row per unique URL visited. It stores aggregate statistics for each URL across all visits.
CREATE TABLE urls (
id INTEGER PRIMARY KEY AUTOINCREMENT,
url LONGVARCHAR,
title LONGVARCHAR,
visit_count INTEGER DEFAULT 0 NOT NULL,
typed_count INTEGER DEFAULT 0 NOT NULL,
last_visit_time INTEGER NOT NULL, -- WebKit timestamp
hidden INTEGER DEFAULT 0 NOT NULL
);
visits Table
The visits table records each individual visit to a URL. This is the primary table for timeline reconstruction.
CREATE TABLE visits (
id INTEGER PRIMARY KEY AUTOINCREMENT,
url INTEGER NOT NULL, -- Foreign key to urls.id
visit_time INTEGER NOT NULL, -- WebKit timestamp
from_visit INTEGER, -- Referring visit ID
transition INTEGER DEFAULT 0 NOT NULL, -- Transition type bitmask
segment_id INTEGER,
visit_duration INTEGER DEFAULT 0 NOT NULL, -- Duration in microseconds
incremented_omnibox_typed_score BOOLEAN DEFAULT FALSE NOT NULL,
opener_visit INTEGER,
originator_cache_guid TEXT,
originator_visit_id INTEGER,
originator_from_visit INTEGER,
originator_opener_visit INTEGER,
is_known_to_sync BOOLEAN DEFAULT FALSE NOT NULL,
consider_for_ntp_most_visited BOOLEAN DEFAULT FALSE NOT NULL,
publicly_routable BOOLEAN DEFAULT FALSE NOT NULL
);
downloads Table
The downloads table records all file download events.
CREATE TABLE downloads (
id INTEGER PRIMARY KEY AUTOINCREMENT,
guid VARCHAR NOT NULL,
current_path LONGVARCHAR NOT NULL,
target_path LONGVARCHAR NOT NULL,
start_time INTEGER NOT NULL, -- WebKit timestamp
received_bytes INTEGER NOT NULL,
total_bytes INTEGER NOT NULL,
state INTEGER NOT NULL, -- 0=in_progress, 1=complete, 2=cancelled, 3=interrupted
danger_type INTEGER NOT NULL,
interrupt_reason INTEGER NOT NULL,
hash BLOB NOT NULL,
end_time INTEGER NOT NULL, -- WebKit timestamp
opened INTEGER NOT NULL,
last_access_time INTEGER NOT NULL,
transient INTEGER NOT NULL,
referrer VARCHAR NOT NULL,
site_url VARCHAR NOT NULL,
tab_url VARCHAR NOT NULL,
tab_referrer_url VARCHAR NOT NULL,
http_method VARCHAR NOT NULL,
by_ext_id VARCHAR NOT NULL,
by_ext_name VARCHAR NOT NULL,
etag VARCHAR NOT NULL,
last_modified VARCHAR NOT NULL,
mime_type VARCHAR(255) NOT NULL,
original_mime_type VARCHAR(255) NOT NULL
);
downloads_url_chains Table
Records redirect chains for downloads, linking the original URL through any intermediate redirects to the final download URL.
CREATE TABLE downloads_url_chains (
id INTEGER NOT NULL, -- Foreign key to downloads.id
chain_index INTEGER NOT NULL,
url LONGVARCHAR NOT NULL,
PRIMARY KEY (id, chain_index)
);
keyword_search_terms Table
Records search queries performed through the omnibox, linked to the URL that was visited as a result.
CREATE TABLE keyword_search_terms (
keyword_id INTEGER NOT NULL,
url_id INTEGER NOT NULL, -- Foreign key to urls.id
term LONGVARCHAR NOT NULL,
normalized_term LONGVARCHAR NOT NULL
);
content_annotations Table (Chrome 91+)
Stores content-level metadata for visits, including page categories and search terms.
CREATE TABLE content_annotations (
visit_id INTEGER PRIMARY KEY,
visibility_score NUMERIC,
categories VARCHAR,
page_topics_model_version INTEGER,
annotation_flags INTEGER NOT NULL DEFAULT 0,
entities VARCHAR,
related_searches VARCHAR,
search_normalized_url VARCHAR,
search_terms LONGVARCHAR,
alternative_title VARCHAR,
page_language VARCHAR,
password_state INTEGER DEFAULT 0 NOT NULL,
has_url_keyed_image BOOLEAN DEFAULT FALSE NOT NULL
);
context_annotations Table (Chrome 92+)
Stores context-level metadata for visits, including duration and browser context.
CREATE TABLE context_annotations (
visit_id INTEGER PRIMARY KEY,
context_annotation_flags INTEGER NOT NULL,
duration_since_last_visit INTEGER,
page_end_reason INTEGER,
total_foreground_duration INTEGER,
browser_type INTEGER DEFAULT 0 NOT NULL,
window_id INTEGER DEFAULT -1 NOT NULL,
tab_id INTEGER DEFAULT -1 NOT NULL,
task_id INTEGER DEFAULT -1 NOT NULL,
root_task_id INTEGER DEFAULT -1 NOT NULL,
parent_task_id INTEGER DEFAULT -1 NOT NULL,
response_code INTEGER DEFAULT 0 NOT NULL
);
Key Fields for Analysis
Visit Analysis
visits.visit_time: The exact timestamp of each visit (WebKit format). This is the primary field for timeline reconstruction.visits.transition: A bitmask indicating how the user arrived at the page. The lower 8 bits encode the core transition type; upper bits encode qualifiers.visits.visit_duration: Time spent on the page in microseconds. Zero means not recorded (common in older versions).visits.from_visit: Links to the referring visit, enabling reconstruction of browsing chains.urls.typed_count: How many times the user explicitly typed this URL, distinguishing intentional navigation from link-following.urls.hidden: Whether the URL is hidden from Chrome's history UI. Hidden entries are still present in the database.
Download Analysis
downloads.state: 0 = in progress, 1 = complete, 2 = cancelled, 3 = interrupted.downloads.danger_type: Indicates whether Chrome flagged the download as dangerous. Values range from 0 (not dangerous) through 19 (blocked unsupported file type).downloads.by_ext_id/downloads.by_ext_name: Identifies downloads initiated by browser extensions.downloads.tab_url: The page the user was viewing when they initiated the download.downloads.opened: Whether the user opened the file after downloading.
Search Term Analysis
keyword_search_terms.term: The raw search query entered by the user.keyword_search_terms.normalized_term: A cleaned/normalised version for deduplication.
Timestamps
All timestamps in the History database use WebKit/Chrome format: microseconds since 1601-01-01 00:00:00 UTC.
unix_microseconds = webkit_timestamp - 11644473600000000
| Field | Format | Notes |
|---|---|---|
urls.last_visit_time | WebKit | Most recent visit to this URL |
visits.visit_time | WebKit | Time of this specific visit |
visits.visit_duration | Microseconds (raw) | Duration on page, not a timestamp |
downloads.start_time | WebKit | When download began |
downloads.end_time | WebKit | When download completed (0 if incomplete) |
downloads.last_access_time | WebKit | When the downloaded file was last accessed |
Transition Types
Chrome encodes the method of navigation as a bitmask in the visits.transition column. Extract the core type from the lower 8 bits: core_type = transition & 0xFF.
| Core Type | Value | Description |
|---|---|---|
| LINK | 0 | User clicked a link on another page |
| TYPED | 1 | User typed the URL in the omnibox |
| AUTO_BOOKMARK | 2 | Auto-navigation from a bookmark |
| AUTO_SUBFRAME | 3 | Automatic subframe navigation (ads, embeds) |
| MANUAL_SUBFRAME | 4 | User-initiated subframe navigation |
| GENERATED | 5 | Navigation generated by script |
| AUTO_TOPLEVEL | 6 | Automatic top-level navigation |
| FORM_SUBMIT | 7 | Form submission |
| RELOAD | 8 | Page reload |
| KEYWORD | 9 | Keyword search from omnibox |
| KEYWORD_GENERATED | 10 | Keyword with URL substitution |
Qualifier flags (OR'd with the core type in upper bits):
| Qualifier | Bitmask | Description |
|---|---|---|
| BLOCKED | 0x00800000 | Navigation was blocked by an extension |
| FORWARD_BACK | 0x01000000 | Forward or back button navigation |
| FROM_ADDRESS_BAR | 0x02000000 | Initiated from the address bar |
| HOME_PAGE | 0x04000000 | Home page navigation |
| FROM_API | 0x08000000 | Initiated by an extension API call |
| CHAIN_START | 0x10000000 | Start of a redirect chain |
| CHAIN_END | 0x20000000 | End of a redirect chain |
| CLIENT_REDIRECT | 0x40000000 | Client-side redirect (JavaScript, meta refresh) |
| SERVER_REDIRECT | 0x80000000 | Server-side redirect (HTTP 301/302) |
Danger Types
The downloads.danger_type field indicates Chrome's safety assessment of each download:
| Type | Value | Description |
|---|---|---|
| NOT_DANGEROUS | 0 | No danger detected |
| DANGEROUS_FILE | 1 | File extension considered dangerous |
| DANGEROUS_URL | 2 | URL flagged by Safe Browsing |
| DANGEROUS_CONTENT | 3 | Content flagged as dangerous |
| MAYBE_DANGEROUS_CONTENT | 4 | Potentially dangerous content |
| UNCOMMON_CONTENT | 5 | Uncommon file type |
| USER_VALIDATED | 6 | User explicitly overrode warning |
| DANGEROUS_HOST | 7 | Host has poor reputation |
| POTENTIALLY_UNWANTED | 8 | Potentially unwanted program |
| ALLOWLISTED_BY_POLICY | 9 | Enterprise policy allowlist |
| BLOCKED_PASSWORD_PROTECTED | 11 | Blocked: password-protected archive |
| BLOCKED_TOO_LARGE | 12 | Blocked: file too large for scanning |
| DEEP_SCANNED_SAFE | 16 | Passed deep content scan |
| DEEP_SCANNED_OPENED_DANGEROUS | 17 | User opened despite scan warning |
Analysis Notes
- The
from_visitfield in thevisitstable allows reconstruction of referral chains. Following this chain reveals how a user navigated from one site to another. - A
typed_count > 0on a URL indicates deliberate user action rather than passive link-following or redirects. - The
hiddenflag is set for URLs that Chrome hides from its own UI (such as certain redirect intermediaries), but these URLs are still forensically relevant. - Downloads with
danger_type = 6(USER_VALIDATED) indicate the user explicitly chose to keep a file that Chrome warned about. - The
by_ext_idfield on downloads can identify if an extension initiated the download, which may be relevant in malware investigations. - Search terms from
keyword_search_termsprovide direct insight into what the user was looking for, without needing to parse search engine URLs.
Version Differences
| Version | Change |
|---|---|
| Chrome 80+ | Baseline schema supported by macfor |
| Chrome 91 | Added content_annotations table with page categories and search metadata |
| Chrome 92 | Added context_annotations table with foreground duration and browser context |
| Chrome 93 | Added clusters table for visit grouping |
| Chrome 100+ | Added originator_* columns to visits for sync attribution |
| Chrome 118+ | Added publicly_routable column to visits |
macfor uses dynamic column detection to handle these differences gracefully, querying only the columns present in the database.
Tool Support
| Tool | Capability |
|---|---|
| macfor | Collects raw database, parses visits with transition types, downloads with danger classification, and search terms |
| Hindsight | Comprehensive Chrome history analysis with timeline output |
| DB Browser for SQLite | Manual inspection of all tables |
| sqlite3 CLI | Command-line queries against the History database |