Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 18 additions & 9 deletions internal/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,25 @@ 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 {
return err
}
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)
}
//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.MkdirAll(filepath.Dir(targetPath), 0755); err != nil {
return err
}

if err = os.WriteFile(targetPath, []byte(content), 0644); err != nil {
return fmt.Errorf("error creating temp file %q: %w", targetPath, err)
}
}

Expand Down
79 changes: 79 additions & 0 deletions internal/handler/handler_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
}