package client import ( "context" "io" "log" "os" "path/filepath" "testing" "time" ) func TestSanitizeKeepsClearOfTheClientsOwnNames(t *testing.T) { for in, want := range map[string]string{ "Team Vault": "Team Vault", "../../etc/passwd": "etc-passwd", "Work.server-v7": "Work-server-v7", "Work.CONFLICT-1": "Work-CONFLICT-1", "nul": "nul-db", "COM3.backup": "COM3.backup-db", ".keeporg-state": "keeporg-state", } { if got := sanitize(in); got != want { t.Errorf("sanitize(%q) = %q, want %q", in, got, want) } } } func TestFileNamesDifferingOnlyInCaseDoNotShareAFile(t *testing.T) { m := &Mirror{st: map[string]*fileState{"a": {Name: "Work", File: "Work.kdbx"}}} if got := m.fileNameFor(Database{ID: "bbbbbbbb", Name: "work"}); got == "work.kdbx" { t.Fatalf("a case variant got the same file on a case-insensitive disk: %s", got) } } // The attack: a database the user lost access to leaves its file behind; an // administrator elsewhere creates an empty database of the same name and // grants it to the user. The leftover must not be adopted and uploaded. func TestARetiredFileIsNotAdoptedByANewDatabase(t *testing.T) { s := newStack(t) s.sync() v, _ := s.remote() if v == 0 { s.write("Team Vault.kdbx", "the original vault") s.sync() } ctx := context.Background() if err := s.admin.DeleteDatabase(ctx, s.dbID, password, ""); err != nil { t.Fatal(err) } s.sync() impostor, err := s.admin.CreateDatabase(ctx, "Team Vault", "everyone", "") if err != nil { t.Fatal(err) } s.sync() if _, v, err := s.admin.Pull(ctx, impostor.ID); err != nil || v != 0 { t.Fatalf("the leftover file was uploaded into the new database: version %d, %v", v, err) } if got := s.read("Team Vault.kdbx"); got != "the original vault" { t.Fatalf("the leftover file changed: %q", got) } } func TestPullDoesNotOverwriteASaveMadeDuringSync(t *testing.T) { dir := t.TempDir() m := &Mirror{dir: dir, log: log.New(io.Discard, "", 0)} path := filepath.Join(dir, "v.kdbx") os.WriteFile(path, []byte("saved just now"), 0o600) // The decision was made on the earlier content. now, _, _ := readFileIfExists(path) if digest(now) == digest([]byte("earlier")) { t.Fatal("test setup") } s := newStack(t) m.api = s.admin if _, err := s.admin.Push(context.Background(), s.dbID, 0, []byte("server copy"), false); err != nil { t.Fatal(err) } m.st, m.status = map[string]*fileState{}, map[string]*Status{} st := &fileState{File: "v.kdbx"} err := m.pull(context.Background(), Database{ID: s.dbID, Name: "v"}, st, path, digest([]byte("earlier"))) if err != errChangedDuringSync { t.Fatalf("pull over a fresh save: %v", err) } if b, _ := os.ReadFile(path); string(b) != "saved just now" { t.Fatalf("the save was overwritten: %q", b) } _ = time.Second }