From c89c1f96543b1bc1af1946098207b8ad10eea1bf Mon Sep 17 00:00:00 2001 From: kapawit Date: Wed, 26 Nov 2025 21:35:38 +0700 Subject: [PATCH 1/2] FIX (postgresql): Escape special characters in .pgpass file for authentication --- .../backups/usecases/postgresql/create_backup_uc.go | 9 ++++++++- .../restores/usecases/postgresql/restore_backup_uc.go | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/backend/internal/features/backups/backups/usecases/postgresql/create_backup_uc.go b/backend/internal/features/backups/backups/usecases/postgresql/create_backup_uc.go index f52c902..87e0adf 100644 --- a/backend/internal/features/backups/backups/usecases/postgresql/create_backup_uc.go +++ b/backend/internal/features/backups/backups/usecases/postgresql/create_backup_uc.go @@ -719,11 +719,18 @@ func (uc *CreatePostgresqlBackupUsecase) createTempPgpassFile( return "", nil } + // Escape special characters in password as per PostgreSQL .pgpass format + // Per official PostgreSQL documentation: only backslash and colon need escaping + escapedPassword := strings.NewReplacer( + "\\", "\\\\", + ":", "\\:", + ).Replace(password) + pgpassContent := fmt.Sprintf("%s:%d:*:%s:%s", pgConfig.Host, pgConfig.Port, pgConfig.Username, - password, + escapedPassword, ) tempDir, err := os.MkdirTemp("", "pgpass") diff --git a/backend/internal/features/restores/usecases/postgresql/restore_backup_uc.go b/backend/internal/features/restores/usecases/postgresql/restore_backup_uc.go index f19b23b..6e06e79 100644 --- a/backend/internal/features/restores/usecases/postgresql/restore_backup_uc.go +++ b/backend/internal/features/restores/usecases/postgresql/restore_backup_uc.go @@ -564,11 +564,18 @@ func (uc *RestorePostgresqlBackupUsecase) createTempPgpassFile( return "", nil } + // Escape special characters in password as per PostgreSQL .pgpass format + // Per official PostgreSQL documentation: only backslash and colon need escaping + escapedPassword := strings.NewReplacer( + "\\", "\\\\", + ":", "\\:", + ).Replace(password) + pgpassContent := fmt.Sprintf("%s:%d:*:%s:%s", pgConfig.Host, pgConfig.Port, pgConfig.Username, - password, + escapedPassword, ) tempDir, err := os.MkdirTemp("", "pgpass") From fa0e3d1ce2cb463182c44720059f3af465b9d88e Mon Sep 17 00:00:00 2001 From: Rostislav Dugin Date: Thu, 27 Nov 2025 17:00:26 +0300 Subject: [PATCH 2/2] REFACTOR (pgpass): Refactor escaping --- .../usecases/postgresql/create_backup_uc.go | 13 +++++-------- .../usecases/postgresql/restore_backup_uc.go | 13 +++++-------- backend/internal/util/tools/postgresql.go | 19 +++++++++++++++++++ frontend/src/shared/theme/ThemeProvider.tsx | 2 ++ 4 files changed, 31 insertions(+), 16 deletions(-) diff --git a/backend/internal/features/backups/backups/usecases/postgresql/create_backup_uc.go b/backend/internal/features/backups/backups/usecases/postgresql/create_backup_uc.go index 87e0adf..02acd7f 100644 --- a/backend/internal/features/backups/backups/usecases/postgresql/create_backup_uc.go +++ b/backend/internal/features/backups/backups/usecases/postgresql/create_backup_uc.go @@ -719,17 +719,14 @@ func (uc *CreatePostgresqlBackupUsecase) createTempPgpassFile( return "", nil } - // Escape special characters in password as per PostgreSQL .pgpass format - // Per official PostgreSQL documentation: only backslash and colon need escaping - escapedPassword := strings.NewReplacer( - "\\", "\\\\", - ":", "\\:", - ).Replace(password) + escapedHost := tools.EscapePgpassField(pgConfig.Host) + escapedUsername := tools.EscapePgpassField(pgConfig.Username) + escapedPassword := tools.EscapePgpassField(password) pgpassContent := fmt.Sprintf("%s:%d:*:%s:%s", - pgConfig.Host, + escapedHost, pgConfig.Port, - pgConfig.Username, + escapedUsername, escapedPassword, ) diff --git a/backend/internal/features/restores/usecases/postgresql/restore_backup_uc.go b/backend/internal/features/restores/usecases/postgresql/restore_backup_uc.go index 6e06e79..a46bd9d 100644 --- a/backend/internal/features/restores/usecases/postgresql/restore_backup_uc.go +++ b/backend/internal/features/restores/usecases/postgresql/restore_backup_uc.go @@ -564,17 +564,14 @@ func (uc *RestorePostgresqlBackupUsecase) createTempPgpassFile( return "", nil } - // Escape special characters in password as per PostgreSQL .pgpass format - // Per official PostgreSQL documentation: only backslash and colon need escaping - escapedPassword := strings.NewReplacer( - "\\", "\\\\", - ":", "\\:", - ).Replace(password) + escapedHost := tools.EscapePgpassField(pgConfig.Host) + escapedUsername := tools.EscapePgpassField(pgConfig.Username) + escapedPassword := tools.EscapePgpassField(password) pgpassContent := fmt.Sprintf("%s:%d:*:%s:%s", - pgConfig.Host, + escapedHost, pgConfig.Port, - pgConfig.Username, + escapedUsername, escapedPassword, ) diff --git a/backend/internal/util/tools/postgresql.go b/backend/internal/util/tools/postgresql.go index 63915b9..e3b1412 100644 --- a/backend/internal/util/tools/postgresql.go +++ b/backend/internal/util/tools/postgresql.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" "runtime" + "strings" env_utils "postgresus-backend/internal/util/env" ) @@ -151,6 +152,24 @@ func VerifyPostgresesInstallation( logger.Info("All PostgreSQL version-specific client tools verification completed successfully!") } +// EscapePgpassField escapes special characters in a field value for .pgpass file format. +// According to PostgreSQL documentation, the .pgpass file format requires: +// - Backslash (\) must be escaped as \\ +// - Colon (:) must be escaped as \: +// Additionally, newlines and carriage returns are removed to prevent format corruption. +func EscapePgpassField(field string) string { + // Remove newlines and carriage returns that would break .pgpass format + field = strings.ReplaceAll(field, "\r", "") + field = strings.ReplaceAll(field, "\n", "") + + // Escape backslashes first (order matters!) + // Then escape colons + field = strings.ReplaceAll(field, "\\", "\\\\") + field = strings.ReplaceAll(field, ":", "\\:") + + return field +} + func getPostgresqlBasePath( version PostgresqlVersion, envMode env_utils.EnvMode, diff --git a/frontend/src/shared/theme/ThemeProvider.tsx b/frontend/src/shared/theme/ThemeProvider.tsx index 9f227d3..e8600fe 100644 --- a/frontend/src/shared/theme/ThemeProvider.tsx +++ b/frontend/src/shared/theme/ThemeProvider.tsx @@ -16,10 +16,12 @@ function getSystemTheme(): ResolvedTheme { function getStoredTheme(): ThemeMode { if (typeof window !== 'undefined') { const stored = localStorage.getItem(THEME_STORAGE_KEY); + if (stored === 'light' || stored === 'dark' || stored === 'system') { return stored; } } + return 'system'; }