fix(database): tighten Postgres init script filename handling

Validate new init-script filenames against path traversal and shell
metacharacters via a new validateFilenameSafe() helper, and harden the
write/delete paths with basename() + escapeshellarg() so legacy rows
still deploy and can be cleaned up without regressions.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Andras BacsaiandClaude Opus 4.7 committed 2026-04-20 21:26:34 +02:00
1 parent b1a78df58e
commit a05d4e3a4b
5 files changed
+297 -10

No files matched your search

@@ -358,9 +358,14 @@ class General extends Component
if ($oldScript && $oldScript['filename'] !== $script['filename']) {
try {
// Validate and escape filename to prevent command injection
validateShellSafePath($oldScript['filename'], 'init script filename');
$old_file_path = "$configuration_dir/docker-entrypoint-initdb.d/{$oldScript['filename']}";
// New filename is user-supplied — must be safe before accepting the rename.
validateFilenameSafe($script['filename'], 'init script filename');
// Old filename may be a legacy value written before this validation existed.
// basename() scopes the rm to the initdb.d directory; escapeshellarg() contains
// any remaining shell-metachars. No validator — don't block cleanup of legacy rows.
$old_filename = basename($oldScript['filename']);
$old_file_path = "$configuration_dir/docker-entrypoint-initdb.d/{$old_filename}";
$escapedOldPath = escapeshellarg($old_file_path);
$delete_command = "rm -f {$escapedOldPath}";
instant_remote_process([$delete_command], $this->server);
@@ -404,9 +409,11 @@ class General extends Component
$configuration_dir = database_configuration_dir().'/'.$container_name;
try {
// Validate and escape filename to prevent command injection
validateShellSafePath($script['filename'], 'init script filename');
$file_path = "$configuration_dir/docker-entrypoint-initdb.d/{$script['filename']}";
// Allow deletion of legacy rows with unsafe filenames so operators can clean up.
// basename() scopes the rm to the initdb.d directory; escapeshellarg() keeps the
// shell invocation safe regardless of the stored value.
$safe_filename = basename($script['filename']);
$file_path = "$configuration_dir/docker-entrypoint-initdb.d/{$safe_filename}";
$escapedPath = escapeshellarg($file_path);
$command = "rm -f {$escapedPath}";
@@ -443,8 +450,8 @@ class General extends Component
]);
try {
// Validate filename to prevent command injection
validateShellSafePath($this->new_filename, 'init script filename');
// Validate filename to prevent path traversal and command injection
validateFilenameSafe($this->new_filename, 'init script filename');
} catch (Exception $e) {
$this->dispatch('error', $e->getMessage());