Files
coolify/tests/Feature/RemoveStoredActivityCommandsMigrationTest.php
T
Andras BacsaiandClaude Opus 5.5 f540c49f68 fix(security): encrypt stored remote commands and remove them after the final attempt
remote_process() stored the full shell command as plain text in
activity_log and kept it for 60 days. Database starts store the compose file
with the database passwords, imports store S3 keys, and the Cloudflare
tunnel setup stores its token.

- Store the command encrypted. The queue worker decrypts it right before the
  SSH call and still reads plain commands of tasks queued before the upgrade.
- Remove the command when the task is finished: after a successful run, in
  failed() when no attempt is left, and for a stopped import. A failed attempt
  that the queue retries keeps it.
- Do not write a command preview into the error log.
- Migration: remove stored commands from activities that no longer run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:41:56 +02:00

41 lines
1.7 KiB
PHP

<?php
use App\Enums\ProcessStatus;
use Carbon\CarbonInterface;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Spatie\Activitylog\Models\Activity;
uses(RefreshDatabase::class);
function storedCommandActivity(string $status, ?CarbonInterface $updatedAt = null): Activity
{
$activity = activity()
->withProperties(['command' => 'echo POSTGRES_PASSWORD=secret', 'type' => 'inline', 'status' => $status, 'server_uuid' => 'server'])
->event('inline')
->log('[]');
if ($updatedAt !== null) {
Activity::query()->whereKey($activity->id)->toBase()->update(['updated_at' => $updatedAt]);
}
return $activity;
}
it('removes stored commands from activities that no longer run', function () {
$finished = storedCommandActivity(ProcessStatus::FINISHED->value);
$failed = storedCommandActivity(ProcessStatus::ERROR->value);
$running = storedCommandActivity(ProcessStatus::IN_PROGRESS->value);
$queued = storedCommandActivity(ProcessStatus::QUEUED->value);
$stuck = storedCommandActivity(ProcessStatus::IN_PROGRESS->value, now()->subDays(2));
(require database_path('migrations/2026_09_28_093936_remove_stored_commands_from_finished_activities.php'))->up();
$command = fn (Activity $activity) => Activity::query()->findOrFail($activity->id)->getExtraProperty('command');
expect($command($finished))->toBeNull()
->and($command($failed))->toBeNull()
->and($command($stuck))->toBeNull()
->and($command($running))->toBe('echo POSTGRES_PASSWORD=secret')
->and($command($queued))->toBe('echo POSTGRES_PASSWORD=secret')
->and(Activity::query()->findOrFail($finished->id)->getExtraProperty('status'))->toBe(ProcessStatus::FINISHED->value);
});