Skip to content

Commit ba24e2f

Browse files
authored
Merge pull request #21 from sparklyi/fix/issue-21-path_traversal_vulnerability
Fix/issue #21 path traversal vulnerability
2 parents 1c5c0ff + 88e44fb commit ba24e2f

2 files changed

Lines changed: 97 additions & 9 deletions

File tree

internal/handler/handler.go

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,25 @@ type Handler struct {
1616
}
1717

1818
func copyFilesToTmpDir(tmpDir string, files map[string]string) error {
19-
for f, src := range files {
20-
in := filepath.Join(tmpDir, f)
21-
if strings.Contains(f, "/") {
22-
if err := os.MkdirAll(filepath.Dir(in), 0755); err != nil {
23-
return err
24-
}
19+
cleanTmpDir, err := filepath.Abs(tmpDir)
20+
if err != nil {
21+
return err
22+
}
23+
24+
for relPath, content := range files {
25+
targetPath := filepath.Join(cleanTmpDir, relPath)
26+
27+
rel, err := filepath.Rel(cleanTmpDir, targetPath)
28+
if err != nil || strings.HasPrefix(rel, "..") {
29+
return fmt.Errorf("invalid file path %q: path traversal detected", relPath)
2530
}
26-
//nolint
27-
if err := os.WriteFile(in, []byte(src), 0644); err != nil {
28-
return fmt.Errorf("error creating temp file %q: %w", in, err)
31+
32+
if err = os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil {
33+
return err
34+
}
35+
36+
if err = os.WriteFile(targetPath, []byte(content), 0644); err != nil {
37+
return fmt.Errorf("error creating temp file %q: %w", targetPath, err)
2938
}
3039
}
3140

internal/handler/handler_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package handler
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"strings"
7+
"testing"
8+
)
9+
10+
func TestCopyFilesToTmpDir_PathTraversal_ShouldBeBlocked(t *testing.T) {
11+
testCases := []struct {
12+
name string
13+
filename string
14+
}{
15+
{"dot-dot-slash", "../escape.txt"},
16+
{"multiple-dot-dot", "../../escape.txt"},
17+
{"deeply-nested-escape", "subdir/../../escape.txt"},
18+
{"dot-dot-in-middle", "foo/../../../escape.txt"},
19+
}
20+
21+
for _, tc := range testCases {
22+
t.Run(tc.name, func(t *testing.T) {
23+
subTmpDir, err := os.MkdirTemp("", "test_sub_sandbox")
24+
if err != nil {
25+
t.Fatalf("failed to create temp dir: %v", err)
26+
}
27+
defer os.RemoveAll(subTmpDir)
28+
29+
files := map[string]string{
30+
tc.filename: "escaped content",
31+
}
32+
33+
err = copyFilesToTmpDir(subTmpDir, files)
34+
35+
if err == nil {
36+
t.Errorf("expected error for path traversal attempt %q, got nil", tc.filename)
37+
} else if !strings.Contains(err.Error(), "path traversal detected") {
38+
t.Errorf("expected 'path traversal detected' error, got: %v", err)
39+
}
40+
41+
resultPath := filepath.Join(subTmpDir, tc.filename)
42+
cleanPath := filepath.Clean(resultPath)
43+
if _, statErr := os.Stat(cleanPath); statErr == nil {
44+
t.Errorf("file should NOT exist at escaped path: %s", cleanPath)
45+
}
46+
})
47+
}
48+
}
49+
50+
func TestCopyFilesToTmpDir_ValidPaths(t *testing.T) {
51+
tmpDir, err := os.MkdirTemp("", "test_sandbox")
52+
if err != nil {
53+
t.Fatalf("failed to create temp dir: %v", err)
54+
}
55+
defer os.RemoveAll(tmpDir)
56+
57+
validFiles := map[string]string{
58+
"main.go": "package main",
59+
"src/util.go": "package src",
60+
"src/lib/helper.go": "package lib",
61+
}
62+
63+
err = copyFilesToTmpDir(tmpDir, validFiles)
64+
if err != nil {
65+
t.Fatalf("copyFilesToTmpDir failed for valid paths: %v", err)
66+
}
67+
68+
for filename, expectedContent := range validFiles {
69+
filePath := filepath.Join(tmpDir, filename)
70+
content, err := os.ReadFile(filePath)
71+
if err != nil {
72+
t.Errorf("failed to read %s: %v", filename, err)
73+
continue
74+
}
75+
if string(content) != expectedContent {
76+
t.Errorf("content mismatch for %s: got %q, want %q", filename, string(content), expectedContent)
77+
}
78+
}
79+
}

0 commit comments

Comments
 (0)