package security import ( "os" "path/filepath" "testing" "github.com/jfraeys/fetch_ml/internal/fileutil" ) func TestSecurePathValidator_ValidatePath(t *testing.T) { // Create a temporary directory for testing tempDir := t.TempDir() validator := fileutil.NewSecurePathValidator(tempDir) tests := []struct { name string input string wantErr bool errMsg string }{ { name: "valid relative path", input: "subdir/file.txt", wantErr: false, }, { name: "valid absolute path within base", input: filepath.Join(tempDir, "file.txt"), wantErr: false, }, { name: "path traversal attempt with dots", input: "../etc/passwd", wantErr: true, errMsg: "path escapes base directory", }, { name: "path traversal attempt with encoded dots", input: "...//...//etc/passwd", wantErr: true, errMsg: "path escapes base directory", }, { name: "absolute path outside base", input: "/etc/passwd", wantErr: true, errMsg: "path escapes base directory", }, { name: "empty path returns base", input: "", wantErr: false, }, { name: "single dot current directory", input: ".", wantErr: false, }, } // Create subdir for tests that need it _ = os.MkdirAll(filepath.Join(tempDir, "subdir"), 0755) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := validator.ValidatePath(tt.input) if tt.wantErr { if err == nil { t.Errorf("ValidatePath() error = nil, wantErr %v", tt.wantErr) return } if tt.errMsg != "" && err.Error()[:len(tt.errMsg)] != tt.errMsg { t.Errorf("ValidatePath() error = %v, want %v", err, tt.errMsg) } } else { if err != nil { t.Errorf("ValidatePath() unexpected error = %v", err) return } if got == "" { t.Errorf("ValidatePath() returned empty path") } } }) } } func TestSecurePathValidator_SymlinkEscape(t *testing.T) { // Create temp directories tempDir := t.TempDir() outsideDir := t.TempDir() validator := fileutil.NewSecurePathValidator(tempDir) // Create a file outside the base directory outsideFile := filepath.Join(outsideDir, "secret.txt") if err := os.WriteFile(outsideFile, []byte("secret"), 0600); err != nil { t.Fatalf("Failed to create outside file: %v", err) } // Create a symlink inside tempDir pointing outside symlinkPath := filepath.Join(tempDir, "link") if err := os.Symlink(outsideFile, symlinkPath); err != nil { t.Fatalf("Failed to create symlink: %v", err) } // Attempt to access through symlink should fail _, err := validator.ValidatePath("link") if err == nil { t.Errorf("Symlink escape should be blocked: %v", err) } } func TestSecurePathValidator_BasePathNotSet(t *testing.T) { validator := fileutil.NewSecurePathValidator("") _, err := validator.ValidatePath("test.txt") if err == nil || err.Error() != "base path not set" { t.Errorf("Expected 'base path not set' error, got: %v", err) } }