package secrets import ( "bytes" "os" "path/filepath" "strings" "testing" ) func testKey(seed byte) []byte { key := make([]byte, 32) for i := range key { key[i] = seed } return key } func readAllSecretStoreBytes(t *testing.T, root string) []byte { t.Helper() var data []byte if err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error { if err != nil { return err } if d.IsDir() { return nil } fileData, err := os.ReadFile(path) if err != nil { return err } data = append(data, fileData...) return nil }); err != nil { t.Fatalf("read secret store bytes: %v", err) } return data } func TestStoreRoundTripsSecretWithoutPlaintextOnDisk(t *testing.T) { root := t.TempDir() store, err := NewStore(root, testKey(0x11)) if err != nil { t.Fatalf("NewStore: %v", err) } const id = "server.password" const value = "s3cr3t-value" if err := store.Write(id, value); err != nil { t.Fatalf("Write: %v", err) } got, err := store.Read(id) if err != nil { t.Fatalf("Read: %v", err) } if got != value { t.Fatalf("Read = %q, want %q", got, value) } raw := readAllSecretStoreBytes(t, root) if bytes.Contains(raw, []byte(value)) { t.Fatal("secret value is stored as plaintext") } if bytes.Contains(raw, []byte(id)) { t.Fatal("secret id is stored as plaintext") } } func TestStoreRejectsWrongKey(t *testing.T) { root := t.TempDir() store, err := NewStore(root, testKey(0x11)) if err != nil { t.Fatalf("NewStore: %v", err) } if err := store.Write("server.password", "s3cr3t-value"); err != nil { t.Fatalf("Write: %v", err) } wrongKeyStore, err := NewStore(root, testKey(0x22)) if err != nil { t.Fatalf("NewStore wrong key: %v", err) } if _, err := wrongKeyStore.Read("server.password"); err == nil { t.Fatal("Read with wrong key succeeded") } } func TestStoreRejectsUnsafeIDs(t *testing.T) { store, err := NewStore(t.TempDir(), testKey(0x11)) if err != nil { t.Fatalf("NewStore: %v", err) } for _, id := range []string{"", ".", "..", "../secret", `folder\secret`, strings.Repeat("a", 257)} { t.Run(id, func(t *testing.T) { if err := store.Write(id, "value"); err == nil { t.Fatalf("Write(%q): expected error", id) } if _, err := store.Read(id); err == nil { t.Fatalf("Read(%q): expected error", id) } }) } } func TestStoreDeletesSecretRecord(t *testing.T) { store, err := NewStore(t.TempDir(), testKey(0x11)) if err != nil { t.Fatalf("NewStore: %v", err) } if err := store.Write("server.password", "s3cr3t-value"); err != nil { t.Fatalf("Write: %v", err) } if err := store.Delete("server.password"); err != nil { t.Fatalf("Delete: %v", err) } if _, err := store.Read("server.password"); err == nil { t.Fatal("Read after Delete succeeded") } list, err := store.ListRecords() if err != nil { t.Fatalf("ListRecords: %v", err) } if len(list) != 0 { t.Fatalf("ListRecords after Delete = %+v, want empty", list) } } func TestStoreListsScopedRecordsWithoutPlaintextOnDisk(t *testing.T) { root := t.TempDir() store, err := NewStore(root, testKey(0x11)) if err != nil { t.Fatalf("NewStore: %v", err) } records := []SecretRecord{ { ID: "global.server-token", Title: "Server Token", Value: "global-secret-value", Scope: SecretScope{Kind: ScopeGlobal}, Username: "root", UpdatedAt: "caller value must be replaced", }, { ID: "client-a.database", Title: "Client A Database", Value: "workspace-secret-value", Scope: SecretScope{ Kind: ScopeWorkspace, WorkspaceRootPath: "ClientA", }, Username: "app", }, } for _, record := range records { if err := store.WriteRecord(record); err != nil { t.Fatalf("WriteRecord(%s): %v", record.ID, err) } } list, err := store.ListRecords() if err != nil { t.Fatalf("ListRecords: %v", err) } if len(list) != 2 { t.Fatalf("ListRecords len = %d, want 2: %+v", len(list), list) } if list[0].Value != "" || list[1].Value != "" { t.Fatalf("ListRecords leaked secret values: %+v", list) } workspaceRecord, err := store.ReadRecord("client-a.database") if err != nil { t.Fatalf("ReadRecord: %v", err) } if workspaceRecord.Value != "workspace-secret-value" { t.Fatalf("workspace secret value = %q", workspaceRecord.Value) } if workspaceRecord.Scope.Kind != ScopeWorkspace || workspaceRecord.Scope.WorkspaceRootPath != "ClientA" { t.Fatalf("workspace scope = %+v", workspaceRecord.Scope) } if workspaceRecord.UpdatedAt == "" || workspaceRecord.UpdatedAt == "caller value must be replaced" { t.Fatalf("UpdatedAt was not set by store: %+v", workspaceRecord) } raw := readAllSecretStoreBytes(t, root) for _, plaintext := range []string{ "global-secret-value", "workspace-secret-value", "global.server-token", "client-a.database", "Client A Database", "ClientA", } { if bytes.Contains(raw, []byte(plaintext)) { t.Fatalf("secret store contains plaintext %q", plaintext) } } } func TestVaultSessionUnlocksWithMasterPasswordOnce(t *testing.T) { root := t.TempDir() session := NewVaultSession(root) if session.Unlocked() { t.Fatal("new session is unlocked") } if _, err := session.Store(); err == nil { t.Fatal("Store before unlock succeeded") } store, err := session.Unlock("correct horse battery staple") if err != nil { t.Fatalf("Unlock first time: %v", err) } if !session.Unlocked() { t.Fatal("session did not stay unlocked") } if err := store.Write("server.password", "s3cr3t-value"); err != nil { t.Fatalf("Write: %v", err) } sameStore, err := session.Store() if err != nil { t.Fatalf("Store after unlock: %v", err) } if got, err := sameStore.Read("server.password"); err != nil || got != "s3cr3t-value" { t.Fatalf("Read after unlock = %q, %v", got, err) } nextSession := NewVaultSession(root) if _, err := nextSession.Unlock("wrong password"); err == nil { t.Fatal("Unlock with wrong password succeeded") } if _, err := nextSession.Unlock("correct horse battery staple"); err != nil { t.Fatalf("Unlock with correct password: %v", err) } raw := readAllSecretStoreBytes(t, root) for _, plaintext := range []string{"correct horse battery staple", "s3cr3t-value"} { if bytes.Contains(raw, []byte(plaintext)) { t.Fatalf("vault session storage contains plaintext %q", plaintext) } } } func TestVaultSessionReportsInitializationAndRejectsWeakInitialPassword(t *testing.T) { root := t.TempDir() session := NewVaultSession(root) initialized, err := session.Initialized() if err != nil { t.Fatalf("Initialized: %v", err) } if initialized { t.Fatal("new secret session is initialized") } if _, err := session.Unlock("123123"); err == nil || !strings.Contains(err.Error(), "at least 8 characters") { t.Fatalf("weak initial Unlock err = %v, want minimum length error", err) } if _, err := session.Unlock("strong password"); err != nil { t.Fatalf("Unlock strong initial password: %v", err) } initialized, err = session.Initialized() if err != nil { t.Fatalf("Initialized after unlock: %v", err) } if !initialized { t.Fatal("secret session was not initialized") } }