browserforensics
Back to all articles

Chrome download history forensics

2026-06-21 · 4 min

Download records answer a question history alone cannot: not just which URL the browser touched, but which bytes landed on disk and where. The downloads and downloads_url_chains tables live inside the History SQLite database — so the artifact you already pulled for visit analysis also carries a complete record of saved files, blocked files, and the redirect chain behind each one.

Where the tables live

The downloads tables are not a separate file. They sit in the same History SQLite database as urls and visits, for both Chrome and Edge:

%LOCALAPPDATA%\Google\Chrome\User Data\Default\History
%LOCALAPPDATA%\Microsoft\Edge\User Data\Default\History

That means one acquisition of History gives you visits and downloads in the same place — see Chrome History database forensics for the broader schema and Chrome History file locations for per-OS paths.

The downloads table

One row per saved file. The columns that carry investigative weight:

ColumnMeaning
idLocal download id (joins downloads_url_chains)
guidStable cross-session identifier
current_pathWhere the file is now on disk
target_pathIntended final path (often a .crdownload rename target)
tab_urlPage the download was initiated from
tab_referrer_urlReferrer of that page
start_time / end_timeWhen the transfer began and finished
received_bytes / total_bytesBytes saved vs. expected size
statein_progress / complete / cancelled / interrupted
danger_typeSafe Browsing verdict (see below)
interrupt_reasonWhy an incomplete download stopped
openedWhether the user opened the file from the browser
last_access_timeLast time the file was accessed via the browser
mime_type / original_mime_typeServed vs. sniffed content type

current_path and target_path diverging is normal — a partial download sits at a temporary name and only takes target_path on completion. A complete row where the two still differ, or where the file no longer exists on disk, is worth a note.

The redirect chain

downloads_url_chains records the full hop sequence that produced the file:

ColumnMeaning
idJoins downloads.id
chain_indexHop order, 0 = the URL the user clicked
urlThe URL at that hop

Order by chain_index for one id and you get click URL → redirects → final fetch URL. This is the column that unmasks a shortener or tracking redirect: the user clicked bit.ly/..., but the last hop is the real host that served the payload. For malware triage, the final hop is the indicator you want, not tab_url.

Reading state, danger and interrupt

state is the headline, but the two qualifier columns explain why a download ended the way it did:

  • danger_type is the Safe Browsing classification — values such as dangerous file, dangerous URL, uncommon content, potentially unwanted, or "not dangerous". A non-benign danger_type paired with complete means the user clicked through a warning and kept the file.
  • interrupt_reason explains an interrupted or cancelled row — network failure, disk full, file blocked by policy, or a Safe Browsing block. A blocked-by-security interrupt_reason is evidence the browser stopped the file, not the user.

Together with opened, these let you separate downloaded, blocked, and downloaded-and-run.

Timestamps

Every time column (start_time, end_time, last_access_time) is a WebKit/Chrome timestamp: microseconds since 1601-01-01 UTC, not Unix epoch. Convert before correlating, or downloads will land 369 years off your other artifacts. See browser timestamp formats for the conversion.

What the tool surfaces

BrowserForensics parses the downloads tables straight from History and shows, per file: the saved-to path, the source URL, byte counts, and the start/end times — all decoded from WebKit time. Those rows feed into the unified timeline alongside visits and cookies, so a download lines up against the page that launched it and the cookies set in the same window. See build a browser activity timeline for how the streams merge.

Pitfalls

  • A cleared download list does not always vacuum the rows immediately — check the WAL before concluding the history is empty.
  • target_path is the browser's intent, not proof the file is still there; verify against the filesystem.
  • received_bytes < total_bytes on a complete row usually means total_bytes was never known (chunked transfer), not a truncated file.
  • Edge shares the exact schema; the only difference is the profile path.

Further reading