diff --git a/app/Events/RestoreJobFinished.php b/app/Events/RestoreJobFinished.php index e17aef904..cc4be8029 100644 --- a/app/Events/RestoreJobFinished.php +++ b/app/Events/RestoreJobFinished.php @@ -22,11 +22,11 @@ public function __construct($data) $commands = []; if (isSafeTmpPath($scriptPath)) { - $commands[] = "docker exec {$container} sh -c 'rm {$scriptPath} 2>/dev/null || true'"; + $commands[] = "docker exec {$container} sh -c 'rm ".escapeshellarg($scriptPath)." 2>/dev/null || true'"; } if (isSafeTmpPath($tmpPath)) { - $commands[] = "docker exec {$container} sh -c 'rm {$tmpPath} 2>/dev/null || true'"; + $commands[] = "docker exec {$container} sh -c 'rm ".escapeshellarg($tmpPath)." 2>/dev/null || true'"; } if (! empty($commands)) { diff --git a/app/Events/S3RestoreJobFinished.php b/app/Events/S3RestoreJobFinished.php index a672f472f..e1f844558 100644 --- a/app/Events/S3RestoreJobFinished.php +++ b/app/Events/S3RestoreJobFinished.php @@ -27,21 +27,21 @@ public function __construct($data) // Ensure helper container is removed (may already be gone from inline cleanup) if (filled($containerName)) { - $commands[] = "docker rm -f {$containerName} 2>/dev/null || true"; + $commands[] = 'docker rm -f '.escapeshellarg($containerName).' 2>/dev/null || true'; } // Clean up server temp file if still exists (should already be cleaned) if (isSafeTmpPath($serverTmpPath)) { - $commands[] = "rm -f {$serverTmpPath} 2>/dev/null || true"; + $commands[] = 'rm -f '.escapeshellarg($serverTmpPath).' 2>/dev/null || true'; } // Clean up any remaining files in database container (may already be cleaned) if (filled($container)) { if (isSafeTmpPath($containerTmpPath)) { - $commands[] = "docker exec {$container} rm -f {$containerTmpPath} 2>/dev/null || true"; + $commands[] = 'docker exec '.escapeshellarg($container).' rm -f '.escapeshellarg($containerTmpPath).' 2>/dev/null || true'; } if (isSafeTmpPath($scriptPath)) { - $commands[] = "docker exec {$container} rm -f {$scriptPath} 2>/dev/null || true"; + $commands[] = 'docker exec '.escapeshellarg($container).' rm -f '.escapeshellarg($scriptPath).' 2>/dev/null || true'; } } diff --git a/app/Livewire/Project/Database/Import.php b/app/Livewire/Project/Database/Import.php index b13c990f6..01ddb7f5d 100644 --- a/app/Livewire/Project/Database/Import.php +++ b/app/Livewire/Project/Database/Import.php @@ -280,6 +280,23 @@ public function loadAvailableS3Storages() } } + public function updatedS3Path($value) + { + // Reset validation state when path changes + $this->s3FileSize = null; + + // Ensure path starts with a slash + if ($value !== null && $value !== '') { + $this->s3Path = str($value)->trim()->start('/')->value(); + } + } + + public function updatedS3StorageId() + { + // Reset validation state when storage changes + $this->s3FileSize = null; + } + public function checkS3File() { if (! $this->s3StorageId) { diff --git a/app/Models/S3Storage.php b/app/Models/S3Storage.php index de27bbca6..47652eb35 100644 --- a/app/Models/S3Storage.php +++ b/app/Models/S3Storage.php @@ -3,6 +3,7 @@ namespace App\Models; use App\Traits\HasSafeStringAttribute; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Support\Facades\Storage; @@ -41,6 +42,19 @@ public function awsUrl() return "{$this->endpoint}/{$this->bucket}"; } + protected function path(): Attribute + { + return Attribute::make( + set: function (?string $value) { + if ($value === null || $value === '') { + return null; + } + + return str($value)->trim()->start('/')->value(); + } + ); + } + public function testConnection(bool $shouldSave = false) { try { diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 29667a581..1b23247fa 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -3250,18 +3250,16 @@ function isSafeTmpPath(?string $path): bool $dirPath = dirname($resolvedPath); // If the directory exists, resolve it via realpath to catch symlink attacks - if (file_exists($resolvedPath) || is_dir($dirPath)) { + if (is_dir($dirPath)) { // For existing paths, resolve to absolute path to catch symlinks - if (is_dir($dirPath)) { - $realDir = realpath($dirPath); - if ($realDir === false) { - return false; - } + $realDir = realpath($dirPath); + if ($realDir === false) { + return false; + } - // Check if the real directory is within /tmp (or its canonical path) - if (! str($realDir)->startsWith('/tmp') && ! str($realDir)->startsWith($canonicalTmpPath)) { - return false; - } + // Check if the real directory is within /tmp (or its canonical path) + if (! str($realDir)->startsWith('/tmp') && ! str($realDir)->startsWith($canonicalTmpPath)) { + return false; } } diff --git a/resources/views/livewire/project/database/import.blade.php b/resources/views/livewire/project/database/import.blade.php index 6e53d516a..b3c21e93e 100644 --- a/resources/views/livewire/project/database/import.blade.php +++ b/resources/views/livewire/project/database/import.blade.php @@ -173,7 +173,7 @@ class="flex-1 p-6 border-2 rounded-sm cursor-pointer transition-all"