From 5456e7ee27ed0ff3f7fbd78da3c3054eda5da562 Mon Sep 17 00:00:00 2001 From: Micah Abbott Date: Thu, 17 Sep 2026 11:42:25 -0400 Subject: [PATCH 1/2] test: cover protocol constants and version Assisted-by: AI Signed-off-by: Micah Abbott --- pkg/protocol/common.go | 4 +- pkg/protocol/common_test.go | 83 +++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 pkg/protocol/common_test.go diff --git a/pkg/protocol/common.go b/pkg/protocol/common.go index 8e6309e..2d66ce1 100644 --- a/pkg/protocol/common.go +++ b/pkg/protocol/common.go @@ -20,7 +20,9 @@ var DeltaHeader = [...]byte{'t', 'a', 'r', 'd', 'f', '1', '\n', 0} var DeltaHeaderv2 = [...]byte{'t', 'a', 'r', 'd', 'f', '2', '\n', 0} // CleanPath cleans up the path lexically and prevents path traversal attacks. -// Any ".." that extends outside the first elements (or the root itself) is invalid and returns "". +// Paths that traverse above the root are sanitized to the corresponding path +// below the root. An empty result is always invalid and callers must reject it; +// it can result from an empty input or a path containing a Windows volume name. // Uses filepath.Clean for proper cross-platform path handling (Windows backslashes, drive letters). // This is a security-critical function used by both tar-diff and tar-patch packages. func CleanPath(pathName string) string { diff --git a/pkg/protocol/common_test.go b/pkg/protocol/common_test.go new file mode 100644 index 0000000..d653be9 --- /dev/null +++ b/pkg/protocol/common_test.go @@ -0,0 +1,83 @@ +package protocol + +import ( + "bytes" + "regexp" + "testing" +) + +func TestDeltaOperationConstants(t *testing.T) { + tests := []struct { + name string + got int + want int + }{ + {name: "data", got: DeltaOpData, want: 0}, + {name: "open", got: DeltaOpOpen, want: 1}, + {name: "copy", got: DeltaOpCopy, want: 2}, + {name: "add data", got: DeltaOpAddData, want: 3}, + {name: "seek", got: DeltaOpSeek, want: 4}, + {name: "zstd dictionary", got: DeltaOpZstdDict, want: 5}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.got != tt.want { + t.Errorf("got %d, want %d", tt.got, tt.want) + } + }) + } +} + +func TestDeltaHeaders(t *testing.T) { + tests := []struct { + name string + got [8]byte + want [8]byte + }{ + {name: "v1", got: DeltaHeader, want: [8]byte{'t', 'a', 'r', 'd', 'f', '1', '\n', 0}}, + {name: "v2", got: DeltaHeaderv2, want: [8]byte{'t', 'a', 'r', 'd', 'f', '2', '\n', 0}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !bytes.Equal(tt.got[:], tt.want[:]) { + t.Errorf("got %q, want %q", tt.got, tt.want) + } + }) + } +} + +func TestVersionFormat(t *testing.T) { + versionPattern := regexp.MustCompile(`^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$`) + if !versionPattern.MatchString(VERSION) { + t.Errorf("VERSION %q does not match the vMAJOR.MINOR.PATCH format", VERSION) + } +} + +func TestCleanPath(t *testing.T) { + tests := []struct { + name string + path string + want string + }{ + {name: "relative", path: "dir/file", want: "dir/file"}, + {name: "absolute", path: "/dir/file", want: "dir/file"}, + {name: "duplicate separators", path: "dir//file", want: "dir/file"}, + {name: "internal traversal", path: "dir/../file", want: "file"}, + {name: "leading traversal", path: "../file", want: "file"}, + {name: "outside root", path: "../../file", want: "file"}, + {name: "empty", path: "", want: ""}, + {name: "root", path: "/", want: ""}, + {name: "current directory", path: ".", want: ""}, + {name: "parent directory", path: "..", want: ""}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := CleanPath(tt.path); got != tt.want { + t.Errorf("CleanPath(%q) = %q, want %q", tt.path, got, tt.want) + } + }) + } +} From 7fc64e898663ab6a529eb749d389351c5fe5a5b9 Mon Sep 17 00:00:00 2001 From: Micah Abbott Date: Tue, 20 Oct 2026 10:23:30 -0400 Subject: [PATCH 2/2] fix: remove unreachable CleanPath fallback Replace the impossible empty fallback with a direct leading slash strip. filepath.Join always supplies the leading slash. Assisted-by: AI Signed-off-by: Micah Abbott --- pkg/protocol/common.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/pkg/protocol/common.go b/pkg/protocol/common.go index 2d66ce1..2331efe 100644 --- a/pkg/protocol/common.go +++ b/pkg/protocol/common.go @@ -41,9 +41,6 @@ func CleanPath(pathName string) string { // Convert back to forward slashes to ensure consistent output clean = filepath.ToSlash(clean) - // We clean the initial slash, making all result relative (or "" which is error) - if len(clean) > 0 && clean[0] == '/' { - return clean[1:] - } - return "" + // Strip the leading slash (filepath.Join always adds one). + return clean[1:] }