From 4513c2f915acb3e0e84edf1c6d6113d4a2e97a48 Mon Sep 17 00:00:00 2001 From: jrmccluskey Date: Wed, 5 Aug 2026 13:42:12 -0400 Subject: [PATCH] [#72] Fix Golang Zip Slip Vulnerability --- .../runtime/xlangx/expansionx/download.go | 17 ++++++++++++- .../xlangx/expansionx/download_test.go | 25 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/sdks/go/pkg/beam/core/runtime/xlangx/expansionx/download.go b/sdks/go/pkg/beam/core/runtime/xlangx/expansionx/download.go index 0b5eba625023..f9a3458500c1 100644 --- a/sdks/go/pkg/beam/core/runtime/xlangx/expansionx/download.go +++ b/sdks/go/pkg/beam/core/runtime/xlangx/expansionx/download.go @@ -139,6 +139,18 @@ func getLocalJar(url string) (string, error) { return jarPath, nil } +func validatePath(dest, filename string) (string, error) { + destPath := filepath.Join(dest, filename) + cleanDest := filepath.Clean(dest) + cleanPath := filepath.Clean(destPath) + + rel, err := filepath.Rel(cleanDest, cleanPath) + if err != nil || strings.HasPrefix(rel, ".."+string(filepath.Separator)) || rel == ".." { + return "", fmt.Errorf("file path %q is outside destination directory %q", filename, dest) + } + return cleanPath, nil +} + func extractJar(source, dest string) error { reader, err := zip.OpenReader(source) if err != nil { @@ -150,7 +162,10 @@ func extractJar(source, dest string) error { } for _, file := range reader.File { - fileName := filepath.Join(dest, file.Name) + fileName, err := validatePath(dest, file.Name) + if err != nil { + return fmt.Errorf("error validating file path (%s, %s): %w", dest, file.Name, err) + } if file.FileInfo().IsDir() { os.MkdirAll(fileName, 0700) continue diff --git a/sdks/go/pkg/beam/core/runtime/xlangx/expansionx/download_test.go b/sdks/go/pkg/beam/core/runtime/xlangx/expansionx/download_test.go index 65e72342a9b9..fc66d53cb6c5 100644 --- a/sdks/go/pkg/beam/core/runtime/xlangx/expansionx/download_test.go +++ b/sdks/go/pkg/beam/core/runtime/xlangx/expansionx/download_test.go @@ -249,3 +249,28 @@ func TestGetPythonVersion(t *testing.T) { } } } + +func TestValidatePath(t *testing.T) { + dest := filepath.Clean("/tmp/cache") + tests := []struct { + name string + filename string + wantErr bool + }{ + {"valid simple file", "Foo.class", false}, + {"valid nested file", "org/apache/beam/Foo.class", false}, + {"traversal attack", "../../etc/passwd", true}, + {"partial directory prefix attack", "../cache_evil/evil.sh", true}, + {"parent directory traversal", "..", true}, + {"nested traversal attack", "foo/bar/../../../etc/passwd", true}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + _, err := validatePath(dest, tc.filename) + if (err != nil) != tc.wantErr { + t.Errorf("validatePath(%q, %q) error = %v, wantErr %v", dest, tc.filename, err, tc.wantErr) + } + }) + } +}