diff --git a/app/Events/RestoreJobFinished.php b/app/Events/RestoreJobFinished.php index 9610c353f..e17aef904 100644 --- a/app/Events/RestoreJobFinished.php +++ b/app/Events/RestoreJobFinished.php @@ -30,7 +30,10 @@ public function __construct($data) } if (! empty($commands)) { - instant_remote_process($commands, Server::find($serverId), throwError: false); + $server = Server::find($serverId); + if ($server) { + instant_remote_process($commands, $server, throwError: false); + } } } } diff --git a/app/Events/S3RestoreJobFinished.php b/app/Events/S3RestoreJobFinished.php index 536af8527..b1ce89c45 100644 --- a/app/Events/S3RestoreJobFinished.php +++ b/app/Events/S3RestoreJobFinished.php @@ -49,7 +49,10 @@ public function __construct($data) } } - instant_remote_process($commands, Server::find($serverId), throwError: false); + $server = Server::find($serverId); + if ($server) { + instant_remote_process($commands, $server, throwError: false); + } } } } diff --git a/app/Jobs/DatabaseBackupJob.php b/app/Jobs/DatabaseBackupJob.php index 8766a1afc..45ac6eb7d 100644 --- a/app/Jobs/DatabaseBackupJob.php +++ b/app/Jobs/DatabaseBackupJob.php @@ -639,7 +639,13 @@ private function upload_to_s3(): void } else { $commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup_log_uuid} --rm -v $this->backup_location:$this->backup_location:ro {$fullImageName}"; } - $commands[] = "docker exec backup-of-{$this->backup_log_uuid} mc alias set temporary {$endpoint} {$key} \"{$secret}\""; + + // Escape S3 credentials to prevent command injection + $escapedEndpoint = escapeshellarg($endpoint); + $escapedKey = escapeshellarg($key); + $escapedSecret = escapeshellarg($secret); + + $commands[] = "docker exec backup-of-{$this->backup_log_uuid} mc alias set temporary {$escapedEndpoint} {$escapedKey} {$escapedSecret}"; $commands[] = "docker exec backup-of-{$this->backup_log_uuid} mc cp $this->backup_location temporary/$bucket{$this->backup_dir}/"; instant_remote_process($commands, $this->server); diff --git a/app/Livewire/Project/Database/Import.php b/app/Livewire/Project/Database/Import.php index d04a1d85d..216d4d5c9 100644 --- a/app/Livewire/Project/Database/Import.php +++ b/app/Livewire/Project/Database/Import.php @@ -187,22 +187,22 @@ public function runImport() try { $this->importRunning = true; $this->importCommands = []; - if (filled($this->customLocation)) { - $backupFileName = '/tmp/restore_'.$this->resource->uuid; - $this->importCommands[] = "docker cp {$this->customLocation} {$this->container}:{$backupFileName}"; - $tmpPath = $backupFileName; - } else { - $backupFileName = "upload/{$this->resource->uuid}/restore"; - $path = Storage::path($backupFileName); - if (! Storage::exists($backupFileName)) { - $this->dispatch('error', 'The file does not exist or has been deleted.'); + $backupFileName = "upload/{$this->resource->uuid}/restore"; - return; - } + // Check if an uploaded file exists first (takes priority over custom location) + if (Storage::exists($backupFileName)) { + $path = Storage::path($backupFileName); $tmpPath = '/tmp/'.basename($backupFileName).'_'.$this->resource->uuid; instant_scp($path, $tmpPath, $this->server); Storage::delete($backupFileName); $this->importCommands[] = "docker cp {$tmpPath} {$this->container}:{$tmpPath}"; + } elseif (filled($this->customLocation)) { + $tmpPath = '/tmp/restore_'.$this->resource->uuid; + $this->importCommands[] = "docker cp {$this->customLocation} {$this->container}:{$tmpPath}"; + } else { + $this->dispatch('error', 'The file does not exist or has been deleted.'); + + return; } // Copy the restore command to a script file @@ -383,7 +383,7 @@ public function restoreFromS3() $commands[] = "docker rm -f {$containerName} 2>/dev/null || true"; // 2. Start helper container on the database network - $commands[] = "docker run -d --network {$destinationNetwork} --name {$containerName} --rm {$fullImageName} sleep 3600"; + $commands[] = "docker run -d --network {$destinationNetwork} --name {$containerName} {$fullImageName} sleep 3600"; // 3. Configure S3 access in helper container $escapedEndpoint = escapeshellarg($endpoint); diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index dc3bb6725..39d847eac 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -3247,10 +3247,12 @@ function isSafeTmpPath(?string $path): bool $canonicalTmpPath = '/tmp'; } + // Calculate dirname once to avoid redundant calls + $dirPath = dirname($resolvedPath); + // If the directory exists, resolve it via realpath to catch symlink attacks - if (file_exists($resolvedPath) || is_dir(dirname($resolvedPath))) { + if (file_exists($resolvedPath) || is_dir($dirPath)) { // For existing paths, resolve to absolute path to catch symlinks - $dirPath = dirname($resolvedPath); if (is_dir($dirPath)) { $realDir = realpath($dirPath); if ($realDir === false) { diff --git a/resources/views/livewire/project/database/import.blade.php b/resources/views/livewire/project/database/import.blade.php index 06faac85f..6e53d516a 100644 --- a/resources/views/livewire/project/database/import.blade.php +++ b/resources/views/livewire/project/database/import.blade.php @@ -29,6 +29,7 @@ }); this.on("addedfile", file => { $wire.isUploading = true; + $wire.customLocation = ''; }); this.on('uploadprogress', function (file, progress, bytesSent) { $wire.progress = progress; @@ -132,8 +133,8 @@ class="flex-1 p-6 border-2 rounded-sm cursor-pointer transition-all"