rfcs/001-project-architecture.md
Author: moonD4rk Status: Living Document Created: 2026-04-05
HackBrowserData is a CLI security research tool that extracts and decrypts browser data from Chromium-based browsers and Firefox across Windows, macOS, and Linux.
Key constraints:
log/slog, slices, maps, cmp) must not be used.browser.DiscoverBrowsersWithKeys() directly; there is no importable pkg/ surface.HackBrowserData/
├── cmd/hack-browser-data/ # CLI entrypoint: cobra root, dump, dumpkeys, archive, restore, list, version
├── browser/ # Browser interface, DiscoverBrowsersWithKeys(), platform browser lists
│ ├── chromium/ # Chromium engine: extraction, decryption, profile discovery
│ ├── firefox/ # Firefox engine: extraction, NSS key derivation
│ └── safari/ # Safari engine: Keychain, Bookmark, History, Downloads (macOS only)
├── types/ # Data model: Category enum, Entry structs, BrowserData
├── crypto/ # Encryption primitives, cipher version detection
├── masterkey/ # Platform-specific master key retrieval (Keychain/DPAPI/D-Bus)
├── filemanager/ # Temp file session, locked file handling (Windows)
├── output/ # Output Writer: CSV, JSON, CookieEditor formatters
├── log/ # Logging with level filtering
└── utils/ # SQLite query helpers, file utilities
Category is an int enum representing 9 browser-agnostic data kinds: Password, Cookie, Bookmark, History, Download, CreditCard, Extension, LocalStorage, SessionStorage.
Three categories are classified as sensitive (Password, Cookie, CreditCard) via IsSensitive(), enabling safe-by-default export scenarios.
Each category has a corresponding Entry struct with json and csv struct tags. All structs are flat (no nesting) and use time.Time for timestamps.
| Struct | Category | Key Fields |
|---|---|---|
LoginEntry | Password | URL, Username, Password, CreatedAt |
CookieEntry | Cookie | Host, Path, Name, Value, IsSecure, IsHTTPOnly, ExpireAt, CreatedAt |
BookmarkEntry | Bookmark | Name, URL, Folder, CreatedAt |
HistoryEntry | History | URL, Title, VisitCount, LastVisit |
DownloadEntry | Download | URL, TargetPath, TotalBytes, StartTime, EndTime |
CreditCardEntry | CreditCard | Name, Number, ExpMonth, ExpYear |
ExtensionEntry | Extension | Name, ID, Description, Version |
StorageEntry | LocalStorage, SessionStorage | URL, Key, Value |
StorageEntry is shared by both LocalStorage and SessionStorage.
BrowserData is the per-profile data container holding typed slices — one per category, populated field-by-field during extraction. Extract() returns []ExtractResult, where each element pairs a Profile identity with a *BrowserData. The output layer uses makeExtractor[T]() generics to pull the correct slice for serialization.
Each config declares an engine kind that determines source paths and extraction logic. Kinds fall into three engine families:
Chromium, ChromiumYandex, ChromiumOpera) — the standard Chromium layout plus two variants that override file names or storage paths for Yandex and Opera forks. See RFC-003.key4.db, SQLite + JSON source files. See RFC-005.See types/category.go for the authoritative enum definition.
BrowserConfig is the declarative, platform-specific browser definition containing: Key (CLI matching; also the Windows ABE / winutil.Table identifier when WindowsABE is true), Name (display), Kind (engine), KeychainLabel (macOS Keychain / Linux D-Bus Secret Service label), WindowsABE (bool — enable Windows App-Bound Encryption v20 path), UserDataDir (data path).
There are two entry points, one for extraction and one for discovery:
DiscoverBrowsersWithKeys(opts) // used by `dump` — ready to Extract
→ discoverFromConfigs(configs, opts) // shared discovery core
→ platformBrowsers() // build-tagged list for this OS
→ filter by name / profile path
→ newBrowser(cfg) // dispatch to chromium/firefox/safari.NewBrowser
→ discoverProfiles() // scan profile subdirectories
→ resolveSourcePaths() // stat candidates, first match wins
→ newCredentialInjector(opts) // build-tagged: returns a browserInjector
→ for each browser: // closure captures retriever + keychain pw lazily
inject(b) // type-assert KeyManager / KeychainPasswordReceiver
DiscoverBrowsers(opts) // used by `list` / `list --detail`
→ discoverFromConfigs(configs, opts) // same shared discovery core, NO injection
DiscoverBrowsersWithKeys does discovery + decryption setup in one call; the returned browsers are ready for b.Extract. DiscoverBrowsers skips injection entirely, so list-style commands never trigger the macOS Keychain password prompt — they have no use for the credential. Both entry points share the same discoverFromConfigs core, so filtering/profile-path/glob semantics stay consistent.
Key design decisions:
newCredentialInjector and reused across every Chromium browser and every profile to prevent repeated keychain prompts on macOS.discoverFromConfigs is injection-free; DiscoverBrowsers stops after it, DiscoverBrowsersWithKeys continues into injection.Preferences files in subdirectories; Firefox accepts any subdirectory containing known source files.Browser configs are defined per-platform via build tags in platformBrowsers() (browser/browser_{darwin,linux,windows}.go). The supported set groups by engine family:
InternetPassword extraction (RFC-006 §7).Adding a new browser is a config-only change in platformBrowsers(); this section does not need updates for new variants within an existing family.
Both Chromium and Firefox engines follow the same per-profile extraction pattern (Firefox runs it inside each profile.extract() call; for Firefox the master key comes from key4.db rather than a platform API):
Extract(categories) // per-profile: one invocation per profile
1. NewSession() → create isolated temp directory
2. acquireFiles(session) → copy source files to temp dir (with dedup and WAL/SHM)
3. getMasterKey(session) → platform-specific key retrieval (Firefox: key4.db)
4. for each category:
extractCategory(data, cat, masterKey, path)
5. defer session.Cleanup() → remove temp directory
For details on file acquisition, see RFC-008. For encryption details, see RFC-003 (Chromium) and RFC-005 (Firefox). For key retrieval, see RFC-006.
The extraction loop maximizes data recovery. Each category is extracted independently — a failure in one does not affect others. Errors are handled at three levels:
| Level | Trigger | Action |
|---|---|---|
| Session failure | Temp dir cannot be created | Abort entirely, return error |
| Category failure | Source file missing or extraction error | Skip category, continue to next |
| Record failure | Single row decryption fails | Skip record, continue extraction |
Master key failure is non-fatal. If the key cannot be retrieved, categories requiring decryption (passwords, cookies, credit cards) produce empty values, while non-encrypted categories (history, bookmarks, downloads) still succeed.
The categoryExtractor interface allows browser-specific extraction logic. Yandex uses custom extractors for passwords and credit cards; Opera uses a custom extractor for extensions. All other categories fall through to the default Chromium implementation.
The module is pinned to go 1.20 in go.mod. This is enforced by a CI lint check that fails if the directive changes.
| Dependency | Version | Purpose |
|---|---|---|
modernc.org/sqlite | v1.31.1 (pinned) | Pure-Go SQLite. v1.32+ requires Go 1.21 |
github.com/syndtr/goleveldb | v1.0.0 | LevelDB for Chromium localStorage/sessionStorage |
github.com/tidwall/gjson | v1.18.0 | JSON path queries |
github.com/spf13/cobra | v1.10.2 | CLI framework |
github.com/moond4rk/keychainbreaker | v0.2.5 | macOS keychain decryption |
github.com/godbus/dbus/v5 | v5.2.2 | Linux D-Bus Secret Service |
golang.org/x/sys | v0.30.0 | Windows syscalls (DPAPI, DuplicateHandle) |
| RFC | Topic |
|---|---|
| RFC-002 | Chromium data file locations and storage formats |
| RFC-003 | Chromium encryption mechanisms per platform |
| RFC-004 | Firefox data file locations and storage formats |
| RFC-005 | Firefox NSS encryption and key derivation |
| RFC-006 | Platform-specific master key retrieval |
| RFC-007 | CLI commands and output formats |
| RFC-008 | File acquisition and platform quirks |
| RFC-009 | Windows locked file bypass technique |