From d38b8d3a2aa0bed7090b4d4d1d29fc577a09c982 Mon Sep 17 00:00:00 2001 From: "devsy-app[bot]" <277138668+devsy-app[bot]@users.noreply.github.com> Date: Sat, 15 Aug 2026 11:03:19 +0000 Subject: [PATCH 1/3] test(extract): cover path-traversal helpers Add direct unit tests for pkg/extract's security-boundary helpers (withinDir, resolveLinkTarget, resolveRelativePath StripLevels branches, validateLinkTarget) that previously had only indirect coverage via Extract. --- pkg/extract/path_test.go | 126 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 pkg/extract/path_test.go diff --git a/pkg/extract/path_test.go b/pkg/extract/path_test.go new file mode 100644 index 000000000..14f9d2fec --- /dev/null +++ b/pkg/extract/path_test.go @@ -0,0 +1,126 @@ +package extract + +import ( + "archive/tar" + "strings" + "testing" +) + +func TestWithinDir(t *testing.T) { + t.Parallel() + const dest = "/tmp/dest" + cases := []struct { + name string + resolved string + want bool + }{ + {"nested inside", dest + "/sub/file", true}, + {"exactly dest", dest, true}, + {"deep inside", dest + "/a/b/c", true}, + {"sibling prefix outside", dest + "_evil/file", false}, + {"unrelated outside", "/tmp/other/file", false}, + {"parent dir", "/tmp", false}, + } + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if got := withinDir(tt.resolved, dest); got != tt.want { + t.Errorf("withinDir(%q, %q) = %v, want %v", tt.resolved, dest, got, tt.want) + } + }) + } +} + +func TestResolveLinkTarget(t *testing.T) { + t.Parallel() + cases := []struct { + name string + linkname string + outFile string + want string + }{ + {"absolute target preserved", "/etc/passwd", "/tmp/dest/link", "/etc/passwd"}, + {"relative parent", "../target", "/tmp/dest/sub/link", "/tmp/dest/target"}, + {"relative sibling", "target.txt", "/tmp/dest/link", "/tmp/dest/target.txt"}, + } + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if got := resolveLinkTarget(tt.linkname, tt.outFile); got != tt.want { + t.Errorf( + "resolveLinkTarget(%q, %q) = %q, want %q", + tt.linkname, + tt.outFile, + got, + tt.want, + ) + } + }) + } +} + +func TestResolveRelativePath_StripLevels(t *testing.T) { + t.Parallel() + cases := []struct { + name string + header *tar.Header + strip int + want string + }{ + {"no strip keeps path", &tar.Header{Name: "dir/file"}, 0, "/dir/file"}, + {"strip one level", &tar.Header{Name: "a/b/c"}, 1, "/b/c"}, + {"strip two levels", &tar.Header{Name: "a/b/c"}, 2, "/c"}, + {"strip beyond depth keeps remainder", &tar.Header{Name: "a"}, 2, "/a"}, + {"strip to final segment", &tar.Header{Name: "a/b"}, 1, "/b"}, + } + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + opts := &Options{StripLevels: tt.strip} + if got := resolveRelativePath(tt.header, opts); got != tt.want { + t.Errorf( + "resolveRelativePath(%q, strip=%d) = %q, want %q", + tt.header.Name, + tt.strip, + got, + tt.want, + ) + } + }) + } +} + +func TestValidateLinkTarget(t *testing.T) { + t.Parallel() + const dest = "/tmp/dest" + cases := []struct { + name string + typeflag byte + linkname string + wantError string + }{ + {"symlink inside allowed", tar.TypeSymlink, "target.txt", ""}, + {"symlink outside blocked", tar.TypeSymlink, "../../etc/passwd", "symlink traversal"}, + {"hard link outside blocked", tar.TypeLink, "../../etc/passwd", "hard link traversal"}, + } + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + header := &tar.Header{Typeflag: tt.typeflag, Name: "link", Linkname: tt.linkname} + outFile := dest + "/link" + err := validateLinkTarget(header, outFile, dest) + if tt.wantError == "" { + if err != nil { + t.Errorf("expected no error, got %v", err) + } + return + } + if err == nil { + t.Fatal("expected error, got nil") + } + if !strings.Contains(err.Error(), tt.wantError) { + t.Errorf("error %q does not mention %q", err, tt.wantError) + } + }) + } +} From f2371609c59317650399b92f6fe090fa60212d9e Mon Sep 17 00:00:00 2001 From: Samuel K Date: Mon, 17 Aug 2026 05:47:28 +0000 Subject: [PATCH 2/3] refactor: lint constants Signed-off-by: Samuel K --- pkg/extract/extract_test.go | 14 +++++++------- pkg/extract/path_test.go | 13 +++++++++---- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/pkg/extract/extract_test.go b/pkg/extract/extract_test.go index 1c7ada5b8..a88067cde 100644 --- a/pkg/extract/extract_test.go +++ b/pkg/extract/extract_test.go @@ -96,7 +96,7 @@ func TestExtract_NormalArchive(t *testing.T) { func TestExtract_PathTraversalBlocked(t *testing.T) { t.Parallel() buf := newTarGz(t, []tarEntry{ - {name: "../../etc/passwd", body: "malicious"}, + {name: etcPasswd, body: "malicious"}, }) dest := t.TempDir() @@ -114,7 +114,7 @@ func TestExtract_SymlinkTraversalBlocked(t *testing.T) { buf := newTarGz(t, []tarEntry{ { name: "evil-link", - linkTarget: "../../etc/passwd", + linkTarget: etcPasswd, symlink: true, }, }) @@ -134,7 +134,7 @@ func TestExtract_HardLinkTraversalBlocked(t *testing.T) { buf := newTarGz(t, []tarEntry{ { name: "evil-link", - linkTarget: "../../etc/passwd", + linkTarget: etcPasswd, symlink: false, }, }) @@ -152,10 +152,10 @@ func TestExtract_HardLinkTraversalBlocked(t *testing.T) { func TestExtract_ValidSymlinkAllowed(t *testing.T) { t.Parallel() buf := newTarGz(t, []tarEntry{ - {name: "target.txt", body: "content"}, + {name: targetFileName, body: "content"}, { name: "link.txt", - linkTarget: "target.txt", + linkTarget: targetFileName, symlink: true, }, }) @@ -170,7 +170,7 @@ func TestExtract_ValidSymlinkAllowed(t *testing.T) { if err != nil { t.Fatalf("readlink: %v", err) } - if target != "target.txt" { - t.Fatalf("symlink target = %q, want %q", target, "target.txt") + if target != targetFileName { + t.Fatalf("symlink target = %q, want %q", target, targetFileName) } } diff --git a/pkg/extract/path_test.go b/pkg/extract/path_test.go index 14f9d2fec..2bbc5f893 100644 --- a/pkg/extract/path_test.go +++ b/pkg/extract/path_test.go @@ -6,6 +6,11 @@ import ( "testing" ) +const ( + targetFileName = "target.txt" + etcPasswd = "../../etc/passwd" +) + func TestWithinDir(t *testing.T) { t.Parallel() const dest = "/tmp/dest" @@ -41,7 +46,7 @@ func TestResolveLinkTarget(t *testing.T) { }{ {"absolute target preserved", "/etc/passwd", "/tmp/dest/link", "/etc/passwd"}, {"relative parent", "../target", "/tmp/dest/sub/link", "/tmp/dest/target"}, - {"relative sibling", "target.txt", "/tmp/dest/link", "/tmp/dest/target.txt"}, + {"relative sibling", targetFileName, "/tmp/dest/link", "/tmp/dest/target.txt"}, } for _, tt := range cases { t.Run(tt.name, func(t *testing.T) { @@ -99,9 +104,9 @@ func TestValidateLinkTarget(t *testing.T) { linkname string wantError string }{ - {"symlink inside allowed", tar.TypeSymlink, "target.txt", ""}, - {"symlink outside blocked", tar.TypeSymlink, "../../etc/passwd", "symlink traversal"}, - {"hard link outside blocked", tar.TypeLink, "../../etc/passwd", "hard link traversal"}, + {"symlink inside allowed", tar.TypeSymlink, targetFileName, ""}, + {"symlink outside blocked", tar.TypeSymlink, etcPasswd, "symlink traversal"}, + {"hard link outside blocked", tar.TypeLink, etcPasswd, "hard link traversal"}, } for _, tt := range cases { t.Run(tt.name, func(t *testing.T) { From 7bf95d327252c18d239612336d50b41846294b7a Mon Sep 17 00:00:00 2001 From: Samuel K Date: Mon, 17 Aug 2026 06:17:40 +0000 Subject: [PATCH 3/3] style: ignore lint error Signed-off-by: Samuel K --- pkg/extract/path_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/extract/path_test.go b/pkg/extract/path_test.go index 2bbc5f893..ef42fd3e2 100644 --- a/pkg/extract/path_test.go +++ b/pkg/extract/path_test.go @@ -8,7 +8,7 @@ import ( const ( targetFileName = "target.txt" - etcPasswd = "../../etc/passwd" + etcPasswd = "../../etc/passwd" // #nosec G101 ) func TestWithinDir(t *testing.T) {