Files
incredigo/internal/pwstore/chromecsv_test.go
T
leetcrypt c2f181e56d passwords: Phase B browser/manager propagation engine (pwgen + pwstore + CLI)
Add the secondary-persona path from docs/BROWSER-ROTATION.md: ingest a password
manager / browser store, take a mandatory verified sealed backup, generate fresh
strong passwords, hand the human a ready-to-finish task at the MFA wall, and commit
the new value into the manager only after the site change is confirmed.

- internal/pwgen: crypto/rand generator (rejection sampling, per-class guarantee,
  vault-native — never returns plaintext as a Go string).
- internal/pwstore: Manager/ItemUpdater/BulkImporter contracts, secrets-free
  RedactIdentity, and five adapters — bitwarden (bw, stdin), keepassxc
  (keepassxc-cli, stdin), 1password (op, argv assignment with documented caveat),
  chrome/firefox (tmpfs CSV ingest-and-shred). All real code; validation status is
  data (MOCK-ONLY against fake binaries/CSVs, recorded in the design doc).
- internal/pwstore/stage.go: age-sealed staged-list (WriteStaged/ReadStaged) +
  StagedImporter — the only artifacts crossing the plan->commit gap, never plaintext.
- cmd/incredigo: passwords scan|plan|guide|commit. Backup gate seals + round-trip
  verifies before any commit; guide is interactive verify-before-commit; commit is
  headless over a sealed stage. Browser CSV writes are gated behind --allow-csv.

Hard rules honored: backup-before-commit, verify-before-commit, no plaintext on
disk (browser CSV is the flag-gated tmpfs+shred exception), MFA always a human
handoff. go vet + -race clean; end-to-end verified against a fake bw.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-19 13:02:18 -07:00

96 lines
2.5 KiB
Go

package pwstore
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
"incredigo/internal/vault"
)
func writeTempCSV(t *testing.T, body string) string {
t.Helper()
p := filepath.Join(t.TempDir(), "chrome-export.csv")
if err := os.WriteFile(p, []byte(body), 0o600); err != nil {
t.Fatal(err)
}
return p
}
func TestChromeExportFromCSV(t *testing.T) {
v := vault.New()
defer v.Purge()
path := writeTempCSV(t,
"name,url,username,password,note\n"+
"GitHub,https://github.com/login,alice@example.com,old1,\n"+
"App,https://app.example.com/,bob,old2,\n")
c := &ChromeCSV{ExportPath: path}
if !c.Available() {
t.Fatal("Available should be true when export file exists")
}
accts, err := c.Export(context.Background(), v)
if err != nil {
t.Fatalf("Export: %v", err)
}
if len(accts) != 2 {
t.Fatalf("got %d accounts, want 2", len(accts))
}
if accts[0].Site != "github.com" {
t.Errorf("site = %q", accts[0].Site)
}
if pw := handleStr(t, v, accts[0].Secret); pw != "old1" {
t.Errorf("password = %q, want old1", pw)
}
}
func TestChromeUnavailableWithoutPath(t *testing.T) {
c := &ChromeCSV{}
if c.Available() {
t.Error("Available should be false with no export path")
}
if _, err := c.Export(context.Background(), vault.New()); err == nil {
t.Error("Export should error with no export path")
}
}
func TestChromeBareImportRefuses(t *testing.T) {
c := &ChromeCSV{}
if err := c.Import(context.Background(), vault.New(), nil); err == nil {
t.Error("bare Import must refuse and direct callers to ImportStaged")
}
}
func TestChromeImportStagedWritesThenShreds(t *testing.T) {
v := vault.New()
defer v.Purge()
c := &ChromeCSV{}
accts := []Account{
{URL: "https://a.test/", Username: "u1", Secret: v.Store([]byte("old1")), Meta: map[string]string{"name": "A"}},
{URL: "https://b.test/", Username: "u2", Secret: v.Store([]byte("old2")), Meta: map[string]string{"name": "B"}},
}
newPw := map[int]*vault.Handle{0: v.Store([]byte("NEW-1"))}
path, cleanup, err := c.ImportStaged(v, accts, newPw)
if err != nil {
t.Fatalf("ImportStaged: %v", err)
}
raw, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read staged csv: %v", err)
}
s := string(raw)
if !strings.Contains(s, "NEW-1") {
t.Errorf("staged csv missing substituted new password:\n%s", s)
}
if !strings.Contains(s, "old2") {
t.Errorf("staged csv missing untouched second password:\n%s", s)
}
cleanup()
if _, err := os.Stat(path); !os.IsNotExist(err) {
t.Errorf("staged csv should be shredded+removed after cleanup, stat err = %v", err)
}
}