Skip to content

Commit df1e26a

Browse files
committed
fix: improve security by adding path validation and restricting file permissions
1 parent 88b66d4 commit df1e26a

File tree

3 files changed

+102
-5
lines changed

3 files changed

+102
-5
lines changed

analyzer.go

+59
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"strings"
1414
"time"
1515

16+
"golang.org/x/mod/modfile"
1617
"golang.org/x/tools/go/packages"
1718
)
1819

@@ -41,6 +42,64 @@ func NewAnalyzer(opts ...Option) *DefaultAnalyzer {
4142
}
4243
}
4344

45+
// validatePath checks if the path is safe to access
46+
func (a *DefaultAnalyzer) validatePath(path string) error {
47+
if path == "" {
48+
return fmt.Errorf("empty path")
49+
}
50+
51+
// Convert to absolute path
52+
absPath := path
53+
if !filepath.IsAbs(path) {
54+
absPath = filepath.Join(a.opts.WorkDir, path)
55+
}
56+
57+
// Clean the path
58+
absPath = filepath.Clean(absPath)
59+
60+
// Check if the path is within workDir
61+
workDirAbs, err := filepath.Abs(a.opts.WorkDir)
62+
if err != nil {
63+
return fmt.Errorf("failed to get absolute path: %w", err)
64+
}
65+
66+
if !strings.HasPrefix(absPath, workDirAbs) {
67+
return fmt.Errorf("path is outside of working directory")
68+
}
69+
70+
return nil
71+
}
72+
73+
// loadGoMod loads and parses the go.mod file
74+
func (a *DefaultAnalyzer) loadGoMod() (*modfile.File, error) {
75+
goModPath := filepath.Join(a.opts.WorkDir, "go.mod")
76+
77+
if err := a.validatePath(goModPath); err != nil {
78+
return nil, fmt.Errorf("invalid go.mod path: %w", err)
79+
}
80+
81+
content, err := os.ReadFile(goModPath)
82+
if err != nil {
83+
return nil, fmt.Errorf("read go.mod: %w", err)
84+
}
85+
86+
// Extract module name from go.mod
87+
var moduleName string
88+
lines := strings.Split(string(content), "\n")
89+
for _, line := range lines {
90+
if strings.HasPrefix(strings.TrimSpace(line), "module ") {
91+
moduleName = strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(line), "module "))
92+
break
93+
}
94+
}
95+
96+
if moduleName == "" {
97+
return nil, &AnalysisError{Op: "parse go.mod", Path: goModPath, Wrapped: fmt.Errorf("module name not found")}
98+
}
99+
100+
return nil, nil
101+
}
102+
44103
// loadPackage loads a package with basic configuration
45104
// It supports both local and third-party packages
46105
func (a *DefaultAnalyzer) loadPackage(pkgPath string) (*packages.Package, error) {

examples/validator/main.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"log"
77
"os"
8+
"path/filepath"
89

910
"github.com/iamlongalong/readgo"
1011
)
@@ -37,9 +38,10 @@ func InvalidFunction() {
3738
}`,
3839
}
3940

40-
// Write test files
41+
// Write test files with secure permissions
4142
for name, content := range files {
42-
if err := os.WriteFile(tmpDir+"/"+name, []byte(content), 0644); err != nil {
43+
filePath := filepath.Join(tmpDir, name)
44+
if err := os.WriteFile(filePath, []byte(content), 0600); err != nil {
4345
log.Fatalf("Failed to write file %s: %v", name, err)
4446
}
4547
}

reader.go

+39-3
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,40 @@ func (r *DefaultReader) WithWorkDir(dir string) *DefaultReader {
2929
return r
3030
}
3131

32+
// validatePath checks if the path is safe to access
33+
func (r *DefaultReader) validatePath(path string) error {
34+
if path == "" {
35+
return fmt.Errorf("empty path")
36+
}
37+
38+
// Convert to absolute path
39+
absPath := path
40+
if !filepath.IsAbs(path) {
41+
absPath = filepath.Join(r.workDir, path)
42+
}
43+
44+
// Clean the path
45+
absPath = filepath.Clean(absPath)
46+
47+
// Check if the path is within workDir
48+
workDirAbs, err := filepath.Abs(r.workDir)
49+
if err != nil {
50+
return fmt.Errorf("failed to get absolute path: %w", err)
51+
}
52+
53+
if !strings.HasPrefix(absPath, workDirAbs) {
54+
return fmt.Errorf("path is outside of working directory")
55+
}
56+
57+
return nil
58+
}
59+
3260
// GetFileTree returns the file tree starting from the given root
3361
func (r *DefaultReader) GetFileTree(ctx context.Context, root string, opts TreeOptions) (*FileTreeNode, error) {
62+
if err := r.validatePath(root); err != nil {
63+
return nil, fmt.Errorf("invalid root path: %w", err)
64+
}
65+
3466
if root == "" {
3567
root = "."
3668
}
@@ -233,11 +265,15 @@ func (r *DefaultReader) SearchFiles(ctx context.Context, pattern string, opts Tr
233265

234266
// ReadSourceFile reads a source file with the specified options
235267
func (r *DefaultReader) ReadSourceFile(ctx context.Context, path string, opts ReadOptions) ([]byte, error) {
236-
if path == "" {
237-
return nil, ErrInvalidInput
268+
if err := r.validatePath(path); err != nil {
269+
return nil, fmt.Errorf("invalid path: %w", err)
270+
}
271+
272+
absPath := path
273+
if !filepath.IsAbs(path) {
274+
absPath = filepath.Join(r.workDir, path)
238275
}
239276

240-
absPath := filepath.Join(r.workDir, path)
241277
info, err := os.Stat(absPath)
242278
if err != nil {
243279
return nil, err

0 commit comments

Comments
 (0)