* refactor(cli): flatten keys export/import into dumpkeys/restore The keys noun-group clashed with the flat dump/list verbs; unify on flat verbs and drop the keys parent. Pure rename, no behavior change. * docs(rfc): add RFC-013 CLI redesign & cross-host restore The accepted design doc for the flat-verb CLI redesign and cross-platform restore.
12 KiB
RFC-013: CLI Redesign — Flat-Verb Surface & Cross-Host Restore
Author: moonD4rk Status: Accepted — implementation pending Created: 2026-06-03
1. Summary
The command-line surface has accreted two grammars: flat task-verbs (dump, list) alongside a noun-grouped keys export / keys import family. This RFC redesigns the whole surface around one grammar — flat verbs — and specifies the cross-host workflow end to end: export master keys on the origin host, archive the minimal data files, and restore (decrypt) them offline on an analyst host of any platform. It also records the structural problem the redesign exists to solve: the set of browsers a command can act on is platform-specific, yet cross-host restore must work for browsers that were never built for the analyst's OS. Breaking changes are accepted (pre-1.0); the method is CLI-first, deriving the internal data model backwards from the chosen surface.
2. Motivation
2.1 Two grammars in one tool
dump and list are flat task-verbs — the verb is the action. keys export / keys import is a noun-then-action grammar that implies a keys resource with sub-operations. Mixing the two in one small CLI reads as inconsistent: there is no data export or browser list to make the noun grammar systematic, so keys stands alone as a special case. The aim is a tool like kubectl — predictable because it is uniformly verb resource — but without kubectl's complexity. For a tool this size, the matching "simple and consistent" choice is uniformly flat verbs.
2.2 The cross-host workflow is half-automated
Cross-host decryption (export keys on the origin, decrypt a copied profile on the analyst host) shipped in #599–#605, but only the keys half is automated. The analyst still copies the origin's profile data by hand — awkward because the live SQLite files are locked on Windows, the full User Data tree is huge (caches), and only a handful of small files actually matter for decryption (#607).
2.3 Restore is bound to the local platform browser table (#606)
The consumer side reuses DiscoverBrowsers, which iterates platformBrowsers() — the browser table for the analyst's OS, selected by build tag (browser_{darwin,windows,linux}.go). A Windows-only fork such as Sogou / QQ / 360 lives only in the Windows table. On macOS, restore -b sogou matches nothing and aborts with no browsers found, even with a valid keys.json and the data supplied explicitly. This is the crux: the browsers a command may act on are platform-specific, but cross-host restore must transcend that — given a key and the data, it should decrypt any Chromium profile regardless of whether that browser exists on the analyst platform.
3. Proposed CLI surface
Six flat verbs, one grammar:
hack-browser-data [flags] # default → dump (also Windows double-click)
dump -b -c -f -d -p --zip # local: decrypt this host's browsers → data
dumpkeys -b -o [--keychain-pw] # origin: master keys → keys.json (stdout default)
archive -b -c -o # origin: minimal decryption-relevant files → zip
restore --keys K (--data-dir D | --data-zip Z) [-b] -c -f -d # analyst: keys.json + data → decrypted
list [--detail]
version
Workflows:
local : hbd dump -b chrome -c cookie,password
cross-host: origin> hbd dumpkeys -o keys.json
origin> hbd archive -b chrome -o data.zip
analyst> hbd restore --keys keys.json --data-zip data.zip -c cookie
The keys parent command is removed: keys export becomes dumpkeys, keys import becomes restore, and a new archive fills the missing data-transport step. dump / list / version keep their current behavior; dump stays the default when no subcommand is given (which also covers the Windows double-click case).
4. The browser-universe model
The resolution to §2.3 is a single rule: the set of browsers a command may act on — its "universe" — matches the nature of that command.
| Command | Browser universe | -b sogou on macOS |
|---|---|---|
dump / dumpkeys / archive / list |
the local platformBrowsers() table (what this OS installs) |
correctly fails — Sogou is not on macOS |
restore |
the keys.json itself (whatever the origin exported) |
succeeds — the dump contains a Sogou vault |
dump, dumpkeys, archive, and list act on browsers installed on this host, so the platform table is the right source and -b's vocabulary is the local set. restore acts on transported artifacts that may have come from any platform, so its universe is the keys.json, and -b validates against the dump's vaults, not the local table. Stated plainly: the browsers you can restore are exactly the browsers in your keys.json. This turns the platform difference from a bug into a property, and it is what makes #606 dissolve rather than be patched.
5. Cross-host artifacts and the restore command
The cross-host producer emits two independent, composable artifacts; the consumer takes both.
dumpkeyswriteskeys.json— the portable master keys (stdout by default forssh origin hbd dumpkeys | …pipelines;-ofor a 0600 file).archivewritesdata.zip— only the decryption-relevant files for the requested-ccategories (Login Data,Cookies,Web Data,History, … plusLocal StateandPreferences), read through the existing locked-file bypass, preserving the relativeUser Datalayout so the zip's internal root is theUser Datadir.restoretakes--keys keys.jsonand the data via two explicit flags,--data-dir <dir>or--data-zip <zip>(mutually exclusive, exactly one required). A zip is extracted to a temporary directory; a directory is used as-is. Because the archive preserves layout,unzip data.zip -d X && restore --data-dir Xequalsrestore --data-zip data.zip.
restore is a separate verb, not a dump --keys mode. Folding it into dump would force one command to carry two mutually-exclusive input modes (-b for local discovery xor --keys/--data for transported artifacts) and dead flags (a --keychain-pw that silently does nothing once keys are supplied — a friction the earlier dump --keys design already hit). One verb, one job keeps each command's flags and help self-contained. restore -b is an optional filter over the dump's vaults, not a required selector, because the dump self-describes what each vault is (§4, §6).
6. The cross-platform identity problem (#606): implementation options
Grounding facts:
- Every browser in the tables resolves to one of three engine kinds. All Windows-only forks (
360,360x,qq,sogou,dc,arc, …) aretypes.Chromium; only Opera isChromiumOperaand Yandex isChromiumYandex. Three kinds cover every fork. - The extraction logic (
sourcesForKind/extractorsForKind) carries no build tags — it is OS-independent. A Sogou profile decrypts through the generic Chromium path with no Sogou-specific code. - So restore needs only the engine kind (one of three) plus the data path and the keys. Everything else in
BrowserConfig(display name, keychain label, ABE flag, default install path) is either a label or is used solely for local key derivation and discovery — all irrelevant once static keys and an explicit data path are supplied.
Two ways to give restore the kind for a browser absent from the analyst's table:
Option A — self-describing dump (chosen). The keys.json vault carries the kind. restore reads it and constructs a generic engine of that kind rooted at the supplied data path; it never consults platformBrowsers(). Minimal, and maximally robust: even a fork this build has never heard of by name still restores as long as its kind is one of the three.
Option B — global, OS-independent browser registry. Split the three per-OS tables into one full-fork registry (carrying kind) plus per-OS views that add paths and ABE flags. -b, help text, and list would then recognize every fork on any OS. This is a larger refactor and is not required for restore (restore always has a keys.json to serve as its universe); it is worth doing only if cross-platform -b / list awareness is a goal in its own right.
Decision: Option A. The keys.json vault carries the engine kind, and restore constructs from it without ever consulting platformBrowsers(). Option B above is the considered, rejected alternative.
This crystallizes the principle that lets cross-platform decryption and the current local mode coexist: one engine constructor (chromium.NewBrowser), two config sources. Local commands feed it configs from the per-OS platformBrowsers() table — unchanged; restore feeds it configs synthesized from the keys.json vaults. The cross-platform capability is an additive second source confined to the restore path, so the local mode is left untouched.
7. Downstream architecture implications (derived from the surface)
Working backwards from the chosen surface:
- keydump struct (
masterkey/dump.go): the vault carries the engine kind so restore can construct without the local table. TheBrowserfield becomes the canonical key (it was the display name), aKindstring field is added (valueschromium/chromium-yandex/chromium-opera), andDumpVersiongoes "1"→"2".UserDataDirandProfilesremain as informational fields. The keys stayV10/V11/V20(Chromium-only; Firefox keys are out of scope, §9). browser/keydump.go:BuildDumprecords the kind; the overlayApplyDump(which mutates locally-discovered browsers) is replaced by a construct-from-dump path that synthesizes aBrowserConfigfrom each vault and builds the engine directly — noplatformBrowsers()dependency. This is the mechanical form of §4.archivereuses the per-category source-path resolution already used by extraction, plus the existing locked-file session and the zip helper.- cmd layer: drop the
keysparent; adddumpkeys,archive,restoreas siblings ofdump/list/version. - Cross-cutting (orthogonal to the taxonomy): a Chromium-import password CSV format (
name,url,username,password,note, #602) and category-aware credential prompting so a no-decryption request never asks for a password (#570).
8. Decisions (2026-06-03)
- The browser-universe model (§4) is adopted:
restore's-bvalidates against the dump, not the local table. - #606 implementation: Option A — self-describing dump (§6).
- keydump vault identity: option 1A —
Browserbecomes the canonical key and aKindfield is added (§7). - Verb names are final:
archiveandrestore.
9. Non-goals / deferred
- Firefox / Safari key export (Firefox keys are per-profile NSS; Safari has no portable key).
- A single self-describing bundle fusing keys + data into one file (the composable two-artifact model is chosen for now).
- Encrypted or signed dump artifacts.
- The global browser registry (§6 Option B), unless adopted for #606.
Related RFCs
| RFC | Topic |
|---|---|
| RFC-007 | The CLI and output design this RFC revises |
| RFC-003 | Cipher version dispatch (v10/v11/v20) consumed by restore |
| RFC-006 | Master-key retrieval the cross-host split externalizes |
| RFC-001 | Browser interface and Extract() orchestration |
| RFC-008 | Locked-file session and CompressDir used by archive |