diff --git a/app/Models/ApplicationDeploymentQueue.php b/app/Models/ApplicationDeploymentQueue.php index 5bf64b2998..451136b5f4 100644 --- a/app/Models/ApplicationDeploymentQueue.php +++ b/app/Models/ApplicationDeploymentQueue.php @@ -179,43 +179,47 @@ class ApplicationDeploymentQueue extends Model private function redactSensitiveInfo($text) { - $text = remove_iip($text); + try { + $text = remove_iip($text); - $app = $this->application; - if (! $app) { - return $text; + $app = $this->application; + if (! $app) { + return $text; + } + + $lockedVars = collect([]); + + if ($app->environment_variables) { + $lockedVars = $lockedVars->merge( + $app->environment_variables + ->where('is_shown_once', true) + ->flatMap(fn (EnvironmentVariable $variable): array => $variable->logRedactionValues()) + ->filter() + ); + } + + if ($this->pull_request_id !== 0 && $app->environment_variables_preview) { + $lockedVars = $lockedVars->merge( + $app->environment_variables_preview + ->where('is_shown_once', true) + ->flatMap(fn (EnvironmentVariable $variable): array => $variable->logRedactionValues()) + ->filter() + ); + } + + foreach ($lockedVars as $key => $value) { + $escapedValue = preg_quote($value, '/'); + $text = preg_replace( + '/'.$escapedValue.'/', + REDACTED, + $text + ); + } + + return is_string($text) ? $text : REDACTED; + } catch (\Throwable) { + return REDACTED; } - - $lockedVars = collect([]); - - if ($app->environment_variables) { - $lockedVars = $lockedVars->merge( - $app->environment_variables - ->where('is_shown_once', true) - ->flatMap(fn (EnvironmentVariable $variable): array => $variable->logRedactionValues()) - ->filter() - ); - } - - if ($this->pull_request_id !== 0 && $app->environment_variables_preview) { - $lockedVars = $lockedVars->merge( - $app->environment_variables_preview - ->where('is_shown_once', true) - ->flatMap(fn (EnvironmentVariable $variable): array => $variable->logRedactionValues()) - ->filter() - ); - } - - foreach ($lockedVars as $key => $value) { - $escapedValue = preg_quote($value, '/'); - $text = preg_replace( - '/'.$escapedValue.'/', - REDACTED, - $text - ); - } - - return $text; } public function addLogEntry(string $message, string $type = 'stdout', bool $hidden = false) diff --git a/app/Traits/ExecuteRemoteCommand.php b/app/Traits/ExecuteRemoteCommand.php index 9b8dd02dfd..0d8dfdc29f 100644 --- a/app/Traits/ExecuteRemoteCommand.php +++ b/app/Traits/ExecuteRemoteCommand.php @@ -22,49 +22,53 @@ trait ExecuteRemoteCommand private function redact_sensitive_info($text) { - $text = remove_iip($text); + try { + $text = remove_iip($text); - if (! isset($this->application)) { - return $text; + if (! isset($this->application)) { + return $text; + } + + $lockedVars = collect([]); + + if (isset($this->application->environment_variables)) { + $lockedVars = $lockedVars->merge( + $this->application->environment_variables + ->where('is_shown_once', true) + ->flatMap(fn (EnvironmentVariable $variable): array => $variable->logRedactionValues()) + ->filter() + ); + } + + if (isset($this->pull_request_id) && $this->pull_request_id !== 0 && isset($this->application->environment_variables_preview)) { + $lockedVars = $lockedVars->merge( + $this->application->environment_variables_preview + ->where('is_shown_once', true) + ->flatMap(fn (EnvironmentVariable $variable): array => $variable->logRedactionValues()) + ->filter() + ); + } + + if (isset($this->remote_secrets_cache)) { + $lockedVars = $lockedVars->merge(array_values(array_filter( + $this->remote_secrets_cache, + static fn (mixed $value): bool => is_string($value) && $value !== '' + ))); + } + + foreach ($lockedVars as $key => $value) { + $escapedValue = preg_quote($value, '/'); + $text = preg_replace( + '/'.$escapedValue.'/', + REDACTED, + $text + ); + } + + return is_string($text) ? $text : REDACTED; + } catch (\Throwable) { + return REDACTED; } - - $lockedVars = collect([]); - - if (isset($this->application->environment_variables)) { - $lockedVars = $lockedVars->merge( - $this->application->environment_variables - ->where('is_shown_once', true) - ->flatMap(fn (EnvironmentVariable $variable): array => $variable->logRedactionValues()) - ->filter() - ); - } - - if (isset($this->pull_request_id) && $this->pull_request_id !== 0 && isset($this->application->environment_variables_preview)) { - $lockedVars = $lockedVars->merge( - $this->application->environment_variables_preview - ->where('is_shown_once', true) - ->flatMap(fn (EnvironmentVariable $variable): array => $variable->logRedactionValues()) - ->filter() - ); - } - - if (isset($this->remote_secrets_cache)) { - $lockedVars = $lockedVars->merge(array_values(array_filter( - $this->remote_secrets_cache, - static fn (mixed $value): bool => is_string($value) && $value !== '' - ))); - } - - foreach ($lockedVars as $key => $value) { - $escapedValue = preg_quote($value, '/'); - $text = preg_replace( - '/'.$escapedValue.'/', - REDACTED, - $text - ); - } - - return $text; } public function execute_remote_command(...$commands) diff --git a/tests/Feature/ApplicationDeploymentControlVarFilteringTest.php b/tests/Feature/ApplicationDeploymentControlVarFilteringTest.php index 1e76bf58d6..35d24eedc4 100644 --- a/tests/Feature/ApplicationDeploymentControlVarFilteringTest.php +++ b/tests/Feature/ApplicationDeploymentControlVarFilteringTest.php @@ -134,6 +134,36 @@ it('redacts generated multiline forms in remote command and output logging', fun } }); +it('keeps deployment logging available if value formatting fails', function () { + [$application, $server] = makeDeploymentControlVarFixture(); + $variable = new class extends EnvironmentVariable + { + public function logRedactionValues(): array + { + throw new RuntimeException('Harmless formatting failure'); + } + }; + $variable->is_shown_once = true; + + $application->setRelation('environment_variables', collect([$variable])); + $deployment = ApplicationDeploymentQueue::create([ + 'deployment_uuid' => 'harmless-formatting-failure', + 'application_id' => $application->id, + 'server_id' => $server->id, + ]); + $deployment->setRelation('application', $application); + $deployment->addLogEntry('Harmless log text'); + + expect(json_decode($deployment->fresh()->logs, true)[0]['output'])->toBe(REDACTED); + + [$job, $reflection] = makeControlVarFilteringJob($application, $server); + readDeploymentJobProperty($job, $reflection, 'application') + ->setRelation('environment_variables', collect([$variable])); + + expect(invokeDeploymentJobMethod($job, $reflection, 'redact_sensitive_info', 'Harmless command text')) + ->toBe(REDACTED); +}); + it('ignores empty and non-string remote secrets when redacting command output', function () { [$application, $server] = makeDeploymentControlVarFixture(); [$job, $reflection] = makeControlVarFilteringJob($application, $server, [