diff --git a/source/packages/com_mokosuitebackup/src/Engine/BackupEngine.php b/source/packages/com_mokosuitebackup/src/Engine/BackupEngine.php index c4724de9..3d43533a 100644 --- a/source/packages/com_mokosuitebackup/src/Engine/BackupEngine.php +++ b/source/packages/com_mokosuitebackup/src/Engine/BackupEngine.php @@ -161,10 +161,20 @@ class BackupEngine foreach ($filesToBackup as $relativePath) { $fullPath = JPATH_ROOT . '/' . $relativePath; - if (is_file($fullPath) && is_readable($fullPath)) { - $archiver->addFile($fullPath, $relativePath); - } else { + if (!is_file($fullPath) || !is_readable($fullPath)) { $skippedFiles++; + continue; + } + + // Sanitize configuration.php — replace credentials with + // placeholders so backups are safe to store or transfer. + // MokoRestore prompts for these values during restore. + if ($relativePath === 'configuration.php') { + $sanitized = self::sanitizeConfiguration($fullPath); + $archiver->addFromString($relativePath, $sanitized); + $this->log('configuration.php sanitized (credentials replaced with placeholders)'); + } else { + $archiver->addFile($fullPath, $relativePath); } } @@ -494,6 +504,58 @@ class BackupEngine } } + /** + * Sanitize configuration.php by replacing sensitive field values with + * [SANITIZED:fieldname] placeholders. Non-sensitive fields (sitename, + * debug, cache, SEF, etc.) are preserved as-is. + * + * @param string $path Absolute path to configuration.php + * + * @return string Sanitized file contents + */ + public static function sanitizeConfiguration(string $path): string + { + $content = file_get_contents($path); + + if ($content === false) { + return ''; + } + + // Fields whose values must be replaced with placeholders. + // Grouped by category for maintainability. + $sensitiveFields = [ + // Database + 'host', 'user', 'password', 'db', + // Security + 'secret', + // SMTP + 'smtpuser', 'smtppass', 'smtphost', + // Proxy + 'proxy_user', 'proxy_pass', + // Redis + 'redis_server_auth', 'session_redis_server_auth', + // Database TLS + 'dbsslkey', 'dbsslcert', 'dbsslca', + ]; + + foreach ($sensitiveFields as $field) { + // Match: public $field = 'value'; (single-quoted) + $content = preg_replace( + '/^(\s*public\s+\$' . preg_quote($field, '/') . '\s*=\s*\').*?(\';)/m', + '$1[SANITIZED:' . $field . ']$2', + $content + ); + // Match: public $field = "value"; (double-quoted) + $content = preg_replace( + '/^(\s*public\s+\$' . preg_quote($field, '/') . '\s*=\s*").*?("\s*;)/m', + '$1[SANITIZED:' . $field . ']$2', + $content + ); + } + + return $content; + } + private function log(string $message): void { $this->log[] = '[' . date('H:i:s') . '] ' . $message; diff --git a/source/packages/com_mokosuitebackup/src/Engine/MokoRestore.php b/source/packages/com_mokosuitebackup/src/Engine/MokoRestore.php index 134c23fa..7134943b 100644 --- a/source/packages/com_mokosuitebackup/src/Engine/MokoRestore.php +++ b/source/packages/com_mokosuitebackup/src/Engine/MokoRestore.php @@ -258,31 +258,29 @@ function actionExtract(array $data): array $count = $zip->numFiles; $zip->close(); - // Try to read existing configuration.php for pre-filling + // Try to read existing configuration.php for pre-filling. + // Sanitized backups contain [SANITIZED:field] placeholders — skip those. $existingConfig = []; $configFile = RESTORE_DIR . '/configuration.php'; if (is_file($configFile)) { $content = file_get_contents($configFile); - if (preg_match('/\$host\s*=\s*\'([^\']*)\'/', $content, $m)) { - $existingConfig['db_host'] = $m[1]; - } + $fieldMap = [ + 'host' => 'db_host', + 'db' => 'db_name', + 'user' => 'db_user', + 'dbprefix' => 'db_prefix', + 'sitename' => 'sitename', + ]; - if (preg_match('/\$db\s*=\s*\'([^\']*)\'/', $content, $m)) { - $existingConfig['db_name'] = $m[1]; - } - - if (preg_match('/\$user\s*=\s*\'([^\']*)\'/', $content, $m)) { - $existingConfig['db_user'] = $m[1]; - } - - if (preg_match('/\$dbprefix\s*=\s*\'([^\']*)\'/', $content, $m)) { - $existingConfig['db_prefix'] = $m[1]; - } - - if (preg_match('/\$sitename\s*=\s*\'([^\']*)\'/', $content, $m)) { - $existingConfig['sitename'] = $m[1]; + foreach ($fieldMap as $phpField => $configKey) { + if (preg_match('/\$' . preg_quote($phpField, '/') . '\s*=\s*\'([^\']*)\'/', $content, $m)) { + // Skip sanitized placeholder values + if (strpos($m[1], '[SANITIZED:') === false) { + $existingConfig[$configKey] = $m[1]; + } + } } } diff --git a/source/packages/com_mokosuitebackup/src/Engine/SteppedBackupEngine.php b/source/packages/com_mokosuitebackup/src/Engine/SteppedBackupEngine.php index 81ba8bf6..2470708b 100644 --- a/source/packages/com_mokosuitebackup/src/Engine/SteppedBackupEngine.php +++ b/source/packages/com_mokosuitebackup/src/Engine/SteppedBackupEngine.php @@ -277,10 +277,19 @@ class SteppedBackupEngine foreach ($batch as $relativePath) { $fullPath = JPATH_ROOT . '/' . $relativePath; - if (is_file($fullPath) && is_readable($fullPath)) { - $zip->addFile($fullPath, $relativePath); - $added++; + if (!is_file($fullPath) || !is_readable($fullPath)) { + continue; } + + // Sanitize Joomla config — replace credentials with placeholders + if (basename($relativePath) === 'configuration.php' && dirname($relativePath) === '.') { + $sanitized = BackupEngine::sanitizeConfiguration($fullPath); + $zip->addFromString($relativePath, $sanitized); + } else { + $zip->addFile($fullPath, $relativePath); + } + + $added++; } $zip->close();