From d8d9db958b6a3baf902167d4e07cbbef90a2808c Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 25 Jul 2026 10:26:48 +0300 Subject: [PATCH 1/2] fix(snapshot): handle rand.Read error in generateID to prevent silent collisions --- internal/snapshot/snapshot.go | 18 +++++++++++++----- internal/snapshot/snapshot_test.go | 23 ++++++++++++++++++++--- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/internal/snapshot/snapshot.go b/internal/snapshot/snapshot.go index 614741d..1b06571 100644 --- a/internal/snapshot/snapshot.go +++ b/internal/snapshot/snapshot.go @@ -24,9 +24,13 @@ const ekoDir = ".eko" // It generates a random 8-hex-char ID, copies the project tree (excluding .eko itself) // into .eko/snapshots//, and returns the snapshot ID and its storage path. func CreateSnapshot() (string, string, error) { - id := generateID() + id, err := generateID() + if err != nil { + return "", "", err + } + base := ekoDir + "/snapshots/" + id - err := util.CopyDir(".", base) + err = util.CopyDir(".", base) if err != nil { return "", "", err } @@ -38,10 +42,14 @@ func CreateSnapshot() (string, string, error) { } // generateID returns a random 8-character hexadecimal string used as a snapshot identifier. -func generateID() string { +func generateID() (string, error) { b := make([]byte, 4) - rand.Read(b) - return hex.EncodeToString(b) + _, err := rand.Read(b) + if err != nil { + return "", err + } + + return hex.EncodeToString(b), nil } // RestoreSnapshot reverts the working directory to the state captured in path. diff --git a/internal/snapshot/snapshot_test.go b/internal/snapshot/snapshot_test.go index d1904a1..b7a2fb0 100644 --- a/internal/snapshot/snapshot_test.go +++ b/internal/snapshot/snapshot_test.go @@ -35,7 +35,11 @@ func setupProject(t *testing.T) string { // TestGenerateID_length checks the hex string is 8 characters (4 bytes → 8 hex). func TestGenerateID_length(t *testing.T) { - id := generateID() + id, err := generateID() + if err != nil { + t.Fatalf("generateID error: %v", err) + } + if len(id) != 8 { t.Errorf("expected 8-char hex id, got %q (len=%d)", id, len(id)) } @@ -43,7 +47,11 @@ func TestGenerateID_length(t *testing.T) { // TestGenerateID_hex checks the result is valid hex. func TestGenerateID_hex(t *testing.T) { - id := generateID() + id, err := generateID() + if err != nil { + t.Fatalf("generateID error: %v", err) + } + for _, c := range id { if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) { t.Errorf("non-hex character %q in id %q", c, id) @@ -53,7 +61,16 @@ func TestGenerateID_hex(t *testing.T) { // TestGenerateID_unique checks two consecutive calls produce different IDs. func TestGenerateID_unique(t *testing.T) { - a, b := generateID(), generateID() + a, err := generateID() + if err != nil { + t.Fatalf("generateID error for a: %v", err) + } + + b, err := generateID() + if err != nil { + t.Fatalf("generateID error for b: %v", err) + } + if a == b { t.Errorf("generateID produced the same value twice: %q", a) } From 6ca9faf96737e177bc6c4006d029f44737dcf150 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 25 Jul 2026 11:12:58 +0300 Subject: [PATCH 2/2] fix(cmd): add missing defer database.Close() to prevent leaks --- cmd/init.go | 3 ++- cmd/restore.go | 1 + cmd/save.go | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/init.go b/cmd/init.go index 999f6dd..acadb99 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -1,9 +1,9 @@ package cmd import ( + "eko/internal/db" "fmt" "os" - "eko/internal/db" "github.com/spf13/cobra" ) @@ -14,6 +14,7 @@ var initCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { os.MkdirAll(".eko/snapshots", 0755) database := db.InitDB() + defer database.Close() database.Exec(` CREATE TABLE IF NOT EXISTS snapshots ( id TEXT PRIMARY KEY, diff --git a/cmd/restore.go b/cmd/restore.go index 7c124f0..4794d8a 100644 --- a/cmd/restore.go +++ b/cmd/restore.go @@ -16,6 +16,7 @@ var restoreCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { id := args[0] database := db.InitDB() + defer database.Close() var path string database.QueryRow("SELECT path FROM snapshots WHERE id=?", id).Scan(&path) err := snapshot.RestoreSnapshot(path) diff --git a/cmd/save.go b/cmd/save.go index 2e50e43..1e54a4e 100644 --- a/cmd/save.go +++ b/cmd/save.go @@ -37,6 +37,7 @@ used with the restore command to revert to this state.`, panic(err) } database := db.InitDB() + defer database.Close() database.Exec( "INSERT INTO snapshots(id, message, path) VALUES (?, ?, ?)", id,