From 0d8d5c8357c6887d575ae48c4828ccdac0969688 Mon Sep 17 00:00:00 2001 From: sparkyi Date: Mon, 9 Feb 2026 14:17:52 +0800 Subject: [PATCH 1/3] fix: path traversal vulnerability in copyFilesToTmpDir allows arbitrary file write --- internal/handler/handler.go | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/internal/handler/handler.go b/internal/handler/handler.go index b56b137..7038cc8 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -16,16 +16,27 @@ type Handler struct { } func copyFilesToTmpDir(tmpDir string, files map[string]string) error { - for f, src := range files { - in := filepath.Join(tmpDir, f) - if strings.Contains(f, "/") { - if err := os.MkdirAll(filepath.Dir(in), 0755); err != nil { + cleanTmpDir, err := filepath.Abs(tmpDir) + if err != nil { + return err + } + + for relPath, content := range files { + targetPath := filepath.Join(cleanTmpDir, relPath) + + rel, err := filepath.Rel(cleanTmpDir, targetPath) + if err != nil || strings.HasPrefix(rel, "..") { + return fmt.Errorf("invalid file path %q: path traversal detected", relPath) + } + + if strings.Contains(relPath, "/") { + if err = os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil { return err } } - //nolint - if err := os.WriteFile(in, []byte(src), 0644); err != nil { - return fmt.Errorf("error creating temp file %q: %w", in, err) + + if err = os.WriteFile(targetPath, []byte(content), 0644); err != nil { + return fmt.Errorf("error creating temp file %q: %w", targetPath, err) } } From e3da328f19a1f6c8d098ba3a779fbfed1c09211c Mon Sep 17 00:00:00 2001 From: sparkyi Date: Mon, 9 Feb 2026 14:21:37 +0800 Subject: [PATCH 2/3] fix: unit test cover --- internal/handler/handler_test.go | 79 ++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 internal/handler/handler_test.go diff --git a/internal/handler/handler_test.go b/internal/handler/handler_test.go new file mode 100644 index 0000000..ce40812 --- /dev/null +++ b/internal/handler/handler_test.go @@ -0,0 +1,79 @@ +package handler + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestCopyFilesToTmpDir_PathTraversal_ShouldBeBlocked(t *testing.T) { + testCases := []struct { + name string + filename string + }{ + {"dot-dot-slash", "../escape.txt"}, + {"multiple-dot-dot", "../../escape.txt"}, + {"deeply-nested-escape", "subdir/../../escape.txt"}, + {"dot-dot-in-middle", "foo/../../../escape.txt"}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + subTmpDir, err := os.MkdirTemp("", "test_sub_sandbox") + if err != nil { + t.Fatalf("failed to create temp dir: %v", err) + } + defer os.RemoveAll(subTmpDir) + + files := map[string]string{ + tc.filename: "escaped content", + } + + err = copyFilesToTmpDir(subTmpDir, files) + + if err == nil { + t.Errorf("expected error for path traversal attempt %q, got nil", tc.filename) + } else if !strings.Contains(err.Error(), "path traversal detected") { + t.Errorf("expected 'path traversal detected' error, got: %v", err) + } + + resultPath := filepath.Join(subTmpDir, tc.filename) + cleanPath := filepath.Clean(resultPath) + if _, statErr := os.Stat(cleanPath); statErr == nil { + t.Errorf("file should NOT exist at escaped path: %s", cleanPath) + } + }) + } +} + +func TestCopyFilesToTmpDir_ValidPaths(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "test_sandbox") + if err != nil { + t.Fatalf("failed to create temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + + validFiles := map[string]string{ + "main.go": "package main", + "src/util.go": "package src", + "src/lib/helper.go": "package lib", + } + + err = copyFilesToTmpDir(tmpDir, validFiles) + if err != nil { + t.Fatalf("copyFilesToTmpDir failed for valid paths: %v", err) + } + + for filename, expectedContent := range validFiles { + filePath := filepath.Join(tmpDir, filename) + content, err := os.ReadFile(filePath) + if err != nil { + t.Errorf("failed to read %s: %v", filename, err) + continue + } + if string(content) != expectedContent { + t.Errorf("content mismatch for %s: got %q, want %q", filename, string(content), expectedContent) + } + } +} From 88e44fb9fbc98ee0c87bbe2d367bcd43fbb46f6e Mon Sep 17 00:00:00 2001 From: sparkyi Date: Mon, 9 Feb 2026 14:25:34 +0800 Subject: [PATCH 3/3] fix: remove redundant logic --- internal/handler/handler.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/internal/handler/handler.go b/internal/handler/handler.go index 7038cc8..b9eb138 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -29,10 +29,8 @@ func copyFilesToTmpDir(tmpDir string, files map[string]string) error { return fmt.Errorf("invalid file path %q: path traversal detected", relPath) } - if strings.Contains(relPath, "/") { - if err = os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil { - return err - } + if err = os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil { + return err } if err = os.WriteFile(targetPath, []byte(content), 0644); err != nil {