diff --git a/cmd/analyze/delete.go b/cmd/analyze/delete.go index 2855a28..00d07b4 100644 --- a/cmd/analyze/delete.go +++ b/cmd/analyze/delete.go @@ -122,12 +122,17 @@ func trashPathWithProgress(root string, counter *int64) (int64, error) { // moveToTrash uses macOS Finder to move a file/directory to Trash. // This is the safest method as it uses the system's native trash mechanism. func moveToTrash(path string) error { + // Validate raw input before Abs resolves ".." components away. + if err := validatePath(path); err != nil { + return err + } + absPath, err := filepath.Abs(path) if err != nil { return fmt.Errorf("failed to resolve path: %w", err) } - // Validate path to prevent path traversal attacks. + // Validate resolved path as well (defense-in-depth). if err := validatePath(absPath); err != nil { return err } diff --git a/cmd/analyze/delete_test.go b/cmd/analyze/delete_test.go index 4132e2e..f6b011f 100644 --- a/cmd/analyze/delete_test.go +++ b/cmd/analyze/delete_test.go @@ -5,6 +5,7 @@ package main import ( "os" "path/filepath" + "strings" "testing" ) @@ -82,6 +83,17 @@ func TestMoveToTrashNonExistent(t *testing.T) { } } +func TestMoveToTrashRejectsTraversal(t *testing.T) { + // Verify the full production path rejects ".." before filepath.Abs resolves it. + err := moveToTrash("/tmp/fakedir/../../../etc/passwd") + if err == nil { + t.Fatal("expected error for path with traversal components") + } + if !strings.Contains(err.Error(), "traversal") { + t.Fatalf("expected traversal error, got: %v", err) + } +} + func TestValidatePath(t *testing.T) { tests := []struct { name string