package services import ( "fmt" "path/filepath" "strings" ) // SafeResolvePath resolves a user-supplied relative path within a base directory. // Returns an error if the resolved path escapes the base directory (path traversal). // The baseDir must be an absolute path. func SafeResolvePath(baseDir, userInput string) (string, error) { if userInput == "" { return "", fmt.Errorf("empty path") } // Reject absolute paths if filepath.IsAbs(userInput) { return "", fmt.Errorf("absolute paths not allowed") } // Clean the user input to resolve . and .. components cleaned := filepath.Clean(userInput) // After cleaning, check if it starts with .. (escapes base) if strings.HasPrefix(cleaned, "..") { return "", fmt.Errorf("path traversal detected") } // Resolve the base directory to an absolute path absBase, err := filepath.Abs(baseDir) if err != nil { return "", fmt.Errorf("invalid base directory: %w", err) } // Join and resolve the full path fullPath := filepath.Join(absBase, cleaned) // Final containment check: the resolved path must be within the base directory absFullPath, err := filepath.Abs(fullPath) if err != nil { return "", fmt.Errorf("invalid resolved path: %w", err) } // Ensure the resolved path is strictly inside the base directory (not the base itself) if !strings.HasPrefix(absFullPath, absBase+string(filepath.Separator)) { return "", fmt.Errorf("path traversal detected") } return absFullPath, nil }