fix(s3): address Copilot review - trailing slashes and shell escaping

This commit is contained in:
Karl Bauer 2025-12-26 16:56:25 +01:00
parent 062ad02e15
commit fa785572c5
4 changed files with 20 additions and 6 deletions

View file

@ -664,12 +664,17 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
// Build S3 path with optional prefix
$s3Path = $bucket;
if (filled($this->s3->path)) {
$pathPrefix = ltrim($this->s3->path, '/');
// Strip leading/trailing slashes to avoid double slashes in path
$pathPrefix = trim($this->s3->path, '/');
$s3Path .= '/'.$pathPrefix;
}
$s3Path .= $this->backup_dir.'/';
$commands[] = "docker exec backup-of-{$this->backup_log_uuid} mc cp $this->backup_location temporary/{$s3Path}";
// Escape the paths to prevent command injection
$escapedBackupLocation = escapeshellarg($this->backup_location);
$escapedS3Path = escapeshellarg("temporary/{$s3Path}");
$commands[] = "docker exec backup-of-{$this->backup_log_uuid} mc cp {$escapedBackupLocation} {$escapedS3Path}";
instant_remote_process($commands, $this->server, true, false, null, disableMultiplexing: true);
$this->s3_uploaded = true;

View file

@ -72,7 +72,11 @@ class S3Storage extends BaseModel
return null;
}
return str($value)->trim()->start('/')->value();
// Trim whitespace and normalize: add leading slash, remove trailing slash
$path = str($value)->trim()->start('/')->rtrim('/')->value();
// Return null if only slashes remain after trimming
return $path === '/' ? null : $path;
}
);
}

View file

@ -211,7 +211,8 @@ function deleteBackupsS3(string|array|null $filenames, S3Storage $s3): void
// Apply path prefix if configured
if (filled($s3->path)) {
$pathPrefix = ltrim($s3->path, '/');
// Strip leading/trailing slashes to avoid double slashes in path
$pathPrefix = trim($s3->path, '/');
$filenames = array_map(function ($filename) use ($pathPrefix) {
// 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

View file

@ -99,9 +99,9 @@ test('S3Storage path attribute handles edge cases', function () {
$s3Storage->path = 'path//to///backup';
expect($s3Storage->path)->toBe('/path//to///backup');
// Path ending with slash
// Path ending with slash - trailing slashes are now stripped
$s3Storage->path = 'backups/coolify/';
expect($s3Storage->path)->toBe('/backups/coolify/');
expect($s3Storage->path)->toBe('/backups/coolify');
// Only whitespace should return null
$s3Storage->path = ' ';
@ -110,4 +110,8 @@ test('S3Storage path attribute handles edge cases', function () {
// Path with dots (valid single dots)
$s3Storage->path = 'path.with.dots';
expect($s3Storage->path)->toBe('/path.with.dots');
// Only slashes should return null
$s3Storage->path = '///';
expect($s3Storage->path)->toBeNull();
});