coolify/tests/Unit/RestoreJobFinishedSecurityTest.php
Andras Bacsai 94560ea6c7 feat: streamline S3 restore with single-step flow and improved UI consistency
Major architectural improvements:
- Merged download and restore into single atomic operation
- Eliminated separate S3DownloadFinished event (redundant)
- Files now transfer directly: S3 → helper container → server → database container
- Removed download progress tracking in favor of unified restore progress

UI/UX improvements:
- Unified restore method selection with visual cards
- Consistent "File Information" display between local and S3 restore
- Single slide-over for all restore operations (removed separate S3 download monitor)
- Better visual feedback with loading states

Security enhancements:
- Added isSafeTmpPath() helper for path traversal protection
- URL decode validation to catch encoded attacks
- Canonical path resolution to prevent symlink attacks
- Comprehensive path validation in all cleanup events

Cleanup improvements:
- S3RestoreJobFinished now handles all cleanup (helper container + all temp files)
- RestoreJobFinished uses new isSafeTmpPath() validation
- CoolifyTask dispatches cleanup events even on job failure
- All cleanup uses non-throwing commands (2>/dev/null || true)

Other improvements:
- S3 storage policy authorization on Show component
- Storage Form properly syncs is_usable state after test
- Removed debug code and improved error handling
- Better command organization and documentation

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 10:05:18 +01:00

61 lines
1.9 KiB
PHP

<?php
/**
* Security tests for RestoreJobFinished event to ensure it uses secure path validation.
*/
describe('RestoreJobFinished event security', function () {
it('validates that safe paths pass validation', function () {
$validPaths = [
'/tmp/restore-backup.sql',
'/tmp/restore-script.sh',
'/tmp/database-dump-'.uniqid().'.sql',
];
foreach ($validPaths as $path) {
expect(isSafeTmpPath($path))->toBeTrue();
}
});
it('validates that malicious paths fail validation', function () {
$maliciousPaths = [
'/tmp/../etc/passwd',
'/tmp/foo/../../etc/shadow',
'/etc/sensitive-file',
'/var/www/config.php',
'/tmp/../../../root/.ssh/id_rsa',
];
foreach ($maliciousPaths as $path) {
expect(isSafeTmpPath($path))->toBeFalse();
}
});
it('rejects URL-encoded path traversal attempts', function () {
$encodedTraversalPaths = [
'/tmp/%2e%2e/etc/passwd',
'/tmp/foo%2f%2e%2e%2f%2e%2e/etc/shadow',
urlencode('/tmp/../etc/passwd'),
];
foreach ($encodedTraversalPaths as $path) {
expect(isSafeTmpPath($path))->toBeFalse();
}
});
it('handles edge cases correctly', function () {
// Too short
expect(isSafeTmpPath('/tmp'))->toBeFalse();
expect(isSafeTmpPath('/tmp/'))->toBeFalse();
// Null/empty
expect(isSafeTmpPath(null))->toBeFalse();
expect(isSafeTmpPath(''))->toBeFalse();
// Null byte injection
expect(isSafeTmpPath("/tmp/file.sql\0../../etc/passwd"))->toBeFalse();
// Valid edge cases
expect(isSafeTmpPath('/tmp/x'))->toBeTrue();
expect(isSafeTmpPath('/tmp/very/deeply/nested/path/to/file.sql'))->toBeTrue();
});
});