From 062ad02e15f0a653485d37995516eac9693581c9 Mon Sep 17 00:00:00 2001 From: Karl Bauer Date: Fri, 26 Dec 2025 14:46:47 +0100 Subject: [PATCH] feat(s3): implemented changes for safety & clarity --- app/Livewire/Storage/Form.php | 3 ++- bootstrap/helpers/databases.php | 2 +- .../views/livewire/storage/form.blade.php | 4 ++-- tests/Unit/S3StorageTest.php | 20 +++++++++++++++++++ 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/app/Livewire/Storage/Form.php b/app/Livewire/Storage/Form.php index 330aabaa4..807bef0d4 100644 --- a/app/Livewire/Storage/Form.php +++ b/app/Livewire/Storage/Form.php @@ -44,7 +44,7 @@ class Form extends Component 'secret' => 'required|max:255', 'bucket' => 'required|max:255', 'endpoint' => 'required|url|max:255', - 'path' => 'nullable|max:255|regex:/^[a-zA-Z0-9\/\-_\.]*$/', + 'path' => ['nullable', 'max:255', 'regex:/^[a-zA-Z0-9\/\-_\.]*$/', 'not_regex:/\.\./'], ]; } @@ -68,6 +68,7 @@ class Form extends Component 'endpoint.max' => 'The Endpoint may not be greater than 255 characters.', 'path.max' => 'The Path Prefix may not be greater than 255 characters.', 'path.regex' => 'The Path Prefix may only contain letters, numbers, slashes (/), dashes (-), underscores (_), and dots (.).', + 'path.not_regex' => 'The Path Prefix may not contain directory traversal sequences (..).', ] ); } diff --git a/bootstrap/helpers/databases.php b/bootstrap/helpers/databases.php index 77b5c5f5b..1d22c364b 100644 --- a/bootstrap/helpers/databases.php +++ b/bootstrap/helpers/databases.php @@ -213,7 +213,7 @@ function deleteBackupsS3(string|array|null $filenames, S3Storage $s3): void if (filled($s3->path)) { $pathPrefix = ltrim($s3->path, '/'); $filenames = array_map(function ($filename) use ($pathPrefix) { - // The filename is the local path (e.g., /var/lib/coolify/backups/databases/...) + // The filename is the backup path (e.g., /data/coolify/backups/databases/...) // We need to prepend the path prefix to match the S3 key structure $cleanFilename = ltrim($filename, '/'); diff --git a/resources/views/livewire/storage/form.blade.php b/resources/views/livewire/storage/form.blade.php index 6278adf93..146c272e3 100644 --- a/resources/views/livewire/storage/form.blade.php +++ b/resources/views/livewire/storage/form.blade.php @@ -42,8 +42,8 @@
+ placeholder="e.g., production or instance-1" + helper="Optional prefix added before the standard backup path (/data/coolify/backups/...). Useful for separating multiple Coolify instances in a single bucket." />
path = 'my-instance_2024.backups'; expect($s3Storage->path)->toBe('/my-instance_2024.backups'); }); + +test('S3Storage path attribute handles edge cases', function () { + $s3Storage = new S3Storage; + + // Multiple consecutive slashes are preserved (validation should catch this) + $s3Storage->path = 'path//to///backup'; + expect($s3Storage->path)->toBe('/path//to///backup'); + + // Path ending with slash + $s3Storage->path = 'backups/coolify/'; + expect($s3Storage->path)->toBe('/backups/coolify/'); + + // Only whitespace should return null + $s3Storage->path = ' '; + expect($s3Storage->path)->toBeNull(); + + // Path with dots (valid single dots) + $s3Storage->path = 'path.with.dots'; + expect($s3Storage->path)->toBe('/path.with.dots'); +});