Skip to content

Commit 55cd7f7

Browse files
committed
fix: add path validation to prevent directory traversal attacks (G304)
- Add validateConfigPath() function to validate config file paths - Add validateFilePath() function to validate file copy operations - Prevent directory traversal attacks with .. path components - Restrict file operations to safe directories (working dir, user config, temp) - Only allow .yaml/.yml extensions for config files - Maintain test compatibility with temp directory access Security improvements: - G304 file inclusion vulnerabilities now have proper path validation - Directory traversal attacks are prevented - File operations are restricted to safe locations - Config files are validated for safe extensions Signed-off-by: longhao <[email protected]>
1 parent dd08a7b commit 55cd7f7

File tree

2 files changed

+99
-0
lines changed

2 files changed

+99
-0
lines changed

internal/cli/start.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"os/signal"
99
"path/filepath"
1010
"runtime"
11+
"strings"
1112
"syscall"
1213
"time"
1314

@@ -330,6 +331,14 @@ func startGoServer(ctx context.Context, serverPort, executorPort string, verbose
330331
}
331332

332333
func copyFile(src, dst string) error {
334+
// Validate source and destination paths to prevent directory traversal
335+
if err := validateFilePath(src, "source"); err != nil {
336+
return fmt.Errorf("invalid source path: %w", err)
337+
}
338+
if err := validateFilePath(dst, "destination"); err != nil {
339+
return fmt.Errorf("invalid destination path: %w", err)
340+
}
341+
333342
sourceFile, err := os.ReadFile(src)
334343
if err != nil {
335344
return err
@@ -338,6 +347,37 @@ func copyFile(src, dst string) error {
338347
return os.WriteFile(dst, sourceFile, 0600)
339348
}
340349

350+
// validateFilePath validates that a file path is safe for operations
351+
func validateFilePath(path, pathType string) error {
352+
// Clean the path to resolve any .. or . components
353+
cleanPath := filepath.Clean(path)
354+
355+
// Check for directory traversal attempts
356+
if strings.Contains(cleanPath, "..") {
357+
return fmt.Errorf("directory traversal not allowed in %s path", pathType)
358+
}
359+
360+
// Get absolute path for further validation
361+
absPath, err := filepath.Abs(cleanPath)
362+
if err != nil {
363+
return fmt.Errorf("failed to get absolute path for %s: %w", pathType, err)
364+
}
365+
366+
// Get current working directory
367+
wd, err := os.Getwd()
368+
if err != nil {
369+
return fmt.Errorf("failed to get working directory: %w", err)
370+
}
371+
372+
// Only allow files within current working directory or its subdirectories
373+
wdAbs, _ := filepath.Abs(wd)
374+
if !strings.HasPrefix(absPath, wdAbs) {
375+
return fmt.Errorf("%s file must be within current working directory", pathType)
376+
}
377+
378+
return nil
379+
}
380+
341381
// parsePort parses a port string to integer
342382
func parsePort(portStr string) (int, error) {
343383
if portStr == "" {

internal/config/config.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"os"
66
"path/filepath"
7+
"strings"
78

89
"gopkg.in/yaml.v3"
910

@@ -167,6 +168,11 @@ func (c *Config) setDefaults() {
167168

168169
// loadFromFile loads configuration from YAML file
169170
func (c *Config) loadFromFile(path string) error {
171+
// Validate file path to prevent directory traversal attacks
172+
if err := validateConfigPath(path); err != nil {
173+
return fmt.Errorf("invalid config path: %w", err)
174+
}
175+
170176
data, err := os.ReadFile(path)
171177
if err != nil {
172178
return err
@@ -175,6 +181,59 @@ func (c *Config) loadFromFile(path string) error {
175181
return yaml.Unmarshal(data, c)
176182
}
177183

184+
// validateConfigPath validates that the config file path is safe
185+
func validateConfigPath(path string) error {
186+
// Clean the path to resolve any .. or . components
187+
cleanPath := filepath.Clean(path)
188+
189+
// Check for directory traversal attempts
190+
if strings.Contains(cleanPath, "..") {
191+
return fmt.Errorf("directory traversal not allowed")
192+
}
193+
194+
// Only allow specific file extensions
195+
ext := strings.ToLower(filepath.Ext(cleanPath))
196+
if ext != ".yaml" && ext != ".yml" {
197+
return fmt.Errorf("only .yaml and .yml files are allowed")
198+
}
199+
200+
// Get absolute path for further validation
201+
absPath, err := filepath.Abs(cleanPath)
202+
if err != nil {
203+
return fmt.Errorf("failed to get absolute path: %w", err)
204+
}
205+
206+
// Get current working directory
207+
wd, err := os.Getwd()
208+
if err != nil {
209+
return fmt.Errorf("failed to get working directory: %w", err)
210+
}
211+
212+
// Allow files in current directory or user config directory
213+
wdAbs, _ := filepath.Abs(wd)
214+
if strings.HasPrefix(absPath, wdAbs) {
215+
return nil
216+
}
217+
218+
// Allow files in user config directory
219+
if configDir, err := os.UserConfigDir(); err == nil {
220+
configDirAbs, _ := filepath.Abs(configDir)
221+
if strings.HasPrefix(absPath, configDirAbs) {
222+
return nil
223+
}
224+
}
225+
226+
// Allow files in system temp directory (for testing)
227+
if tempDir := os.TempDir(); tempDir != "" {
228+
tempDirAbs, _ := filepath.Abs(tempDir)
229+
if strings.HasPrefix(absPath, tempDirAbs) {
230+
return nil
231+
}
232+
}
233+
234+
return fmt.Errorf("config file must be in current directory, user config directory, or temp directory")
235+
}
236+
178237
// loadFromEnv loads configuration from environment variables
179238
func (c *Config) loadFromEnv() {
180239
if host := os.Getenv("WEBHOOK_BRIDGE_HOST"); host != "" {

0 commit comments

Comments
 (0)