feat: sanitize configuration.php in backups
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 4s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 5s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 5s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Universal: Auto Version Bump / Version Bump (push) Successful in 12s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 8s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Has been cancelled
Joomla: Extension CI / PHPStan Analysis (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 4s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 5s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 5s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Universal: Auto Version Bump / Version Bump (push) Successful in 12s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 8s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Has been cancelled
Joomla: Extension CI / PHPStan Analysis (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Strip sensitive credentials from configuration.php before adding it to the backup archive. Replaced fields use [SANITIZED:field] placeholders: - Database: host, user, password, db - Security: secret - SMTP: smtpuser, smtppass, smtphost - Proxy: proxy_user, proxy_pass - Redis: redis_server_auth, session_redis_server_auth - DB TLS: dbsslkey, dbsslcert, dbsslca Non-sensitive fields (sitename, debug, cache, SEF, paths, etc.) are preserved as-is for inspection and partial restores. MokoRestore detects sanitized placeholders and leaves those form fields blank so the user must enter fresh credentials (like the Joomla installer). In-Joomla restore is unaffected because RestoreEngine preserves the current site's configuration.php. Applied to both BackupEngine (synchronous) and SteppedBackupEngine (AJAX-based) code paths.
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user