Improve deployment log value handling

This commit is contained in:
Andras Bacsai
2026-09-24 09:29:23 +02:00
parent ad38f799c1
commit b37d24d258
4 changed files with 99 additions and 4 deletions
+2 -2
View File
@@ -192,7 +192,7 @@ class ApplicationDeploymentQueue extends Model
$lockedVars = $lockedVars->merge(
$app->environment_variables
->where('is_shown_once', true)
->pluck('real_value', 'key')
->flatMap(fn (EnvironmentVariable $variable): array => $variable->logRedactionValues())
->filter()
);
}
@@ -201,7 +201,7 @@ class ApplicationDeploymentQueue extends Model
$lockedVars = $lockedVars->merge(
$app->environment_variables_preview
->where('is_shown_once', true)
->pluck('real_value', 'key')
->flatMap(fn (EnvironmentVariable $variable): array => $variable->logRedactionValues())
->filter()
);
}
+24
View File
@@ -309,6 +309,30 @@ class EnvironmentVariable extends BaseModel
return $real_value;
}
/** @return array<int, string> */
public function logRedactionValues(): array
{
$value = $this->real_value;
if (! is_string($value) || $value === '') {
return [];
}
$values = [$value];
if ($this->is_multiline || $this->is_literal) {
$unquoted = str_starts_with($value, "'") && str_ends_with($value, "'")
? substr($value, 1, -1)
: $value;
$values[] = $unquoted;
$values[] = escapeBashEnvValue($unquoted);
$values[] = str_replace(["\r\n", "\r", "\n"], ['\\n', '\\n', '\\n'], $unquoted);
if ($this->is_multiline) {
$values = array_merge($values, preg_split('/\r\n|\r|\n/', $unquoted) ?: []);
}
}
return array_values(array_unique(array_filter($values, static fn (string $item): bool => $item !== '')));
}
public function resolveReferencedValue(): ?string
{
$value = $this->value;
+3 -2
View File
@@ -5,6 +5,7 @@ namespace App\Traits;
use App\Enums\ApplicationDeploymentStatus;
use App\Exceptions\DeploymentException;
use App\Helpers\SshMultiplexingHelper;
use App\Models\EnvironmentVariable;
use App\Models\Server;
use Carbon\Carbon;
use Illuminate\Support\Collection;
@@ -33,7 +34,7 @@ trait ExecuteRemoteCommand
$lockedVars = $lockedVars->merge(
$this->application->environment_variables
->where('is_shown_once', true)
->pluck('real_value', 'key')
->flatMap(fn (EnvironmentVariable $variable): array => $variable->logRedactionValues())
->filter()
);
}
@@ -42,7 +43,7 @@ trait ExecuteRemoteCommand
$lockedVars = $lockedVars->merge(
$this->application->environment_variables_preview
->where('is_shown_once', true)
->pluck('real_value', 'key')
->flatMap(fn (EnvironmentVariable $variable): array => $variable->logRedactionValues())
->filter()
);
}
@@ -10,6 +10,7 @@ use App\Models\EnvironmentVariable;
use App\Models\Project;
use App\Models\Server;
use App\Models\Team;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
@@ -64,6 +65,75 @@ it('redacts resolved remote secrets from command output', function () {
->toBe('token='.REDACTED);
});
it('does not retain locked values from generated build-time debug logs', function () {
config()->set('app.env', 'local');
[$application, $server] = makeDeploymentControlVarFixture();
createApplicationEnvironmentVariable($application, [
'key' => 'SINGLE_MARKER',
'value' => 'harmless-single-marker',
'is_shown_once' => true,
]);
createApplicationEnvironmentVariable($application, [
'key' => 'MULTILINE_MARKER',
'value' => "harmless-first-marker\nharmless-second-marker",
'is_multiline' => true,
'is_shown_once' => true,
]);
[$job, $reflection] = makeControlVarFilteringJob($application, $server);
invokeDeploymentJobMethod($job, $reflection, 'generate_buildtime_environment_variables');
$deployment = ApplicationDeploymentQueue::create([
'deployment_uuid' => 'harmless-debug-log-deployment',
'application_id' => $application->id,
'server_id' => $server->id,
]);
foreach ($job->recordedLogEntries as $entry) {
$deployment->addLogEntry($entry);
}
$retainedLogs = $deployment->fresh()->logs;
expect($retainedLogs)
->not->toContain('harmless-single-marker')
->not->toContain('harmless-first-marker')
->not->toContain('harmless-second-marker')
->toContain(REDACTED);
$member = User::factory()->create();
$application->team()->members()->attach($member->id, ['role' => 'member']);
$application->settings->update(['is_debug_enabled' => true]);
$this->actingAs($member);
$visibleLines = decode_remote_command_output($deployment->fresh())->pluck('line')->implode("\n");
expect($visibleLines)
->toContain('[DEBUG]')
->not->toContain('harmless-first-marker')
->not->toContain('harmless-second-marker');
});
it('redacts generated multiline forms in remote command and output logging', function () {
[$application, $server] = makeDeploymentControlVarFixture();
createApplicationEnvironmentVariable($application, [
'key' => 'MULTILINE_MARKER',
'value' => "harmless-first-marker\nharmless-second-marker",
'is_multiline' => true,
'is_shown_once' => true,
]);
[$job, $reflection] = makeControlVarFilteringJob($application, $server);
$generated = invokeDeploymentJobMethod($job, $reflection, 'generate_buildtime_environment_variables')
->first(fn (string $line): bool => str_starts_with($line, 'MULTILINE_MARKER='));
foreach ([$generated, str_replace("\n", '\\n', $generated), 'harmless-second-marker'] as $loggedForm) {
$redacted = invokeDeploymentJobMethod($job, $reflection, 'redact_sensitive_info', 'output: '.$loggedForm);
expect($redacted)
->not->toContain('harmless-first-marker')
->not->toContain('harmless-second-marker')
->toContain('output: ');
}
});
it('ignores empty and non-string remote secrets when redacting command output', function () {
[$application, $server] = makeDeploymentControlVarFixture();
[$job, $reflection] = makeControlVarFilteringJob($application, $server, [