pwstore: generic csvManager for 8 password-manager CSV adapters
One data-driven csvManager (layout = header + record func) covers edge/brave (chrome layout), safari, lastpass, dashlane, protonpass, nordpass, roboform, and read/guide-only enpass — replacing 8 near-identical files. Export is header-name driven (parseLoginCSV + new aliases); import stages a per-manager-layout CSV on tmpfs and shreds it, gated by --allow-csv. Notes now round-trip through Meta. Reimporter exposes per-manager re-import hints. Column layouts are MOCK-ONLY (unit-validated) on the same LIVE-VM staging machinery; recorded as DATA in BROWSER-ROTATION.md §8. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -53,15 +53,19 @@ func passwordsCmd() *cobra.Command {
|
||||
"passwords -> guided handoff (you change the site + complete MFA) -> commit the new\n" +
|
||||
"value into the manager ONLY after you confirm the site accepted it. MFA/CAPTCHA are\n" +
|
||||
"always your job; incredigo never bypasses them.\n\n" +
|
||||
"--manager bitwarden|1password|keepassxc|chrome|firefox selects the backend.\n" +
|
||||
"CLI managers update one item in place over stdin (no plaintext file). Browser stores\n" +
|
||||
"can only be re-imported via a CSV, so chrome/firefox require --allow-csv (the file is\n" +
|
||||
"written to tmpfs and securely shredded).",
|
||||
"--manager selects the backend:\n" +
|
||||
" in-place (CLI, no plaintext file): bitwarden | 1password | keepassxc\n" +
|
||||
" CSV import/export (tmpfs + shred, needs --allow-csv + --export-path):\n" +
|
||||
" chrome | firefox | edge | brave | safari | lastpass | dashlane |\n" +
|
||||
" protonpass | nordpass | roboform | enpass(read-only)\n" +
|
||||
"CLI managers update one item in place over stdin (no plaintext file). CSV stores can\n" +
|
||||
"only be re-imported via a CSV, so they require --allow-csv (the file is written to\n" +
|
||||
"tmpfs and securely shredded after you confirm re-import).",
|
||||
}
|
||||
pf := c.PersistentFlags()
|
||||
pf.StringVar(&flagPwManager, "manager", "", "backend: bitwarden|1password|keepassxc|chrome|firefox")
|
||||
pf.BoolVar(&flagAllowCSV, "allow-csv", false, "allow the flag-gated browser-CSV plaintext path (tmpfs + shred)")
|
||||
pf.StringVar(&flagExportPath, "export-path", "", "chrome/firefox: CSV you exported from the browser")
|
||||
pf.StringVar(&flagPwManager, "manager", "", "backend: bitwarden|1password|keepassxc|chrome|firefox|edge|brave|safari|lastpass|dashlane|protonpass|nordpass|roboform|enpass")
|
||||
pf.BoolVar(&flagAllowCSV, "allow-csv", false, "allow the flag-gated browser/manager-CSV plaintext path (tmpfs + shred)")
|
||||
pf.StringVar(&flagExportPath, "export-path", "", "CSV managers: the CSV you exported from the browser/manager")
|
||||
pf.StringVar(&flagKdbxPath, "kdbx", "", "keepassxc: path to the .kdbx database")
|
||||
c.AddCommand(pwScanCmd(), pwPlanCmd(), pwGuideCmd(), pwCommitCmd())
|
||||
return c
|
||||
@@ -106,8 +110,17 @@ func buildManager(v *vault.Vault) (pwstore.Manager, error) {
|
||||
}
|
||||
return &pwstore.FirefoxCSV{ExportPath: flagExportPath}, nil
|
||||
case "":
|
||||
return nil, fmt.Errorf("--manager is required (bitwarden|1password|keepassxc|chrome|firefox)")
|
||||
return nil, fmt.Errorf("--manager is required (bitwarden|1password|keepassxc|chrome|firefox|lastpass|dashlane|protonpass|nordpass|roboform|safari|edge|brave|enpass)")
|
||||
default:
|
||||
// CSV-based managers (lastpass, dashlane, protonpass, nordpass, roboform, safari,
|
||||
// edge, brave, enpass) all share the generic csvManager + tmpfs/shred staging.
|
||||
name := strings.ToLower(strings.TrimSpace(flagPwManager))
|
||||
if pwstore.IsCSV(name) {
|
||||
if flagExportPath == "" {
|
||||
return nil, fmt.Errorf("%s requires --export-path <csv> exported from %s", name, name)
|
||||
}
|
||||
return pwstore.NewCSV(name, flagExportPath)
|
||||
}
|
||||
return nil, fmt.Errorf("unknown --manager %q", flagPwManager)
|
||||
}
|
||||
}
|
||||
@@ -522,7 +535,13 @@ func pwCommitCmd() *cobra.Command {
|
||||
defer cleanup()
|
||||
log.Write(audit.Entry{Action: "pw-import", Source: m.Name(), Location: path,
|
||||
Outcome: fmt.Sprintf("staged %d account(s); plaintext CSV on tmpfs, will shred", len(sub))})
|
||||
fmt.Printf("Re-import this CSV into the browser, then press Enter to shred it:\n %s\n", path)
|
||||
where := "Re-import this CSV into the browser/manager"
|
||||
if r, ok := m.(pwstore.Reimporter); ok {
|
||||
if h := r.ReimportHint(); h != "" {
|
||||
where = h
|
||||
}
|
||||
}
|
||||
fmt.Printf("%s, then press Enter to shred it:\n %s\n", where, path)
|
||||
if term.IsTerminal(int(os.Stdin.Fd())) {
|
||||
in := bufio.NewScanner(os.Stdin)
|
||||
in.Scan()
|
||||
|
||||
Reference in New Issue
Block a user