feat(deployment): add command_hidden flag to hide command text in logs

Add support for hiding sensitive command text while preserving output logs.
When command_hidden is true, the command text is set to null in the main log
entry but logged separately to the deployment queue with proper redaction.

- Add command_hidden parameter to execute_remote_command and executeCommandWithProcess
- When enabled, separates command visibility from output visibility
- Fix operator precedence in type ternary expression
This commit is contained in:
Andras Bacsai 2026-03-25 16:48:49 +01:00
parent fbdcf0de51
commit 333cc9589d
2 changed files with 16 additions and 11 deletions

View file

@ -783,7 +783,7 @@ private function deploy_docker_compose_buildpack()
try {
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "cd {$this->workdir} && {$start_command}"), 'hidden' => true],
[executeInDocker($this->deployment_uuid, "cd {$this->workdir} && {$start_command}"), 'hidden' => false, 'type' => 'stdout', 'command_hidden' => true],
);
} catch (\RuntimeException $e) {
if (str_contains($e->getMessage(), "matching `'") || str_contains($e->getMessage(), 'unexpected EOF')) {
@ -801,7 +801,7 @@ private function deploy_docker_compose_buildpack()
$command .= " --env-file {$server_workdir}/.env";
$command .= " --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d";
$this->execute_remote_command(
['command' => $command, 'hidden' => true],
['command' => $command, 'hidden' => false, 'type' => 'stdout', 'command_hidden' => true],
);
}
} else {
@ -818,11 +818,11 @@ private function deploy_docker_compose_buildpack()
$this->write_deployment_configurations();
if ($this->preserveRepository) {
$this->execute_remote_command(
['command' => "cd {$server_workdir} && {$start_command}", 'hidden' => true],
['command' => "cd {$server_workdir} && {$start_command}", 'hidden' => false, 'type' => 'stdout', 'command_hidden' => true],
);
} else {
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$start_command}"), 'hidden' => true],
[executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$start_command}"), 'hidden' => false, 'type' => 'stdout', 'command_hidden' => true],
);
}
} else {
@ -834,14 +834,14 @@ private function deploy_docker_compose_buildpack()
$this->write_deployment_configurations();
$this->execute_remote_command(
['command' => $command, 'hidden' => true],
['command' => $command, 'hidden' => false, 'type' => 'stdout', 'command_hidden' => true],
);
} else {
// Always use .env file
$command .= " --env-file {$this->workdir}/.env";
$command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d";
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, $command), 'hidden' => true],
[executeInDocker($this->deployment_uuid, $command), 'hidden' => false, 'type' => 'stdout', 'command_hidden' => true],
);
$this->write_deployment_configurations();
}

View file

@ -78,6 +78,7 @@ public function execute_remote_command(...$commands)
$customType = data_get($single_command, 'type');
$ignore_errors = data_get($single_command, 'ignore_errors', false);
$append = data_get($single_command, 'append', true);
$command_hidden = data_get($single_command, 'command_hidden', false);
$this->save = data_get($single_command, 'save');
if ($this->server->isNonRoot()) {
if (str($command)->startsWith('docker exec')) {
@ -102,7 +103,7 @@ public function execute_remote_command(...$commands)
while ($attempt < $maxRetries && ! $commandExecuted) {
try {
$this->executeCommandWithProcess($command, $hidden, $customType, $append, $ignore_errors);
$this->executeCommandWithProcess($command, $hidden, $customType, $append, $ignore_errors, $command_hidden);
$commandExecuted = true;
} catch (\RuntimeException|DeploymentException $e) {
$lastError = $e;
@ -152,10 +153,14 @@ public function execute_remote_command(...$commands)
/**
* Execute the actual command with process handling
*/
private function executeCommandWithProcess($command, $hidden, $customType, $append, $ignore_errors)
private function executeCommandWithProcess($command, $hidden, $customType, $append, $ignore_errors, $command_hidden = false)
{
if ($command_hidden && isset($this->application_deployment_queue)) {
$this->application_deployment_queue->addLogEntry('[CMD]: '.$this->redact_sensitive_info($command), hidden: true);
}
$remote_command = SshMultiplexingHelper::generateSshCommand($this->server, $command);
$process = Process::timeout(config('constants.ssh.command_timeout'))->idleTimeout(3600)->start($remote_command, function (string $type, string $output) use ($command, $hidden, $customType, $append) {
$process = Process::timeout(config('constants.ssh.command_timeout'))->idleTimeout(3600)->start($remote_command, function (string $type, string $output) use ($command, $hidden, $customType, $append, $command_hidden) {
$output = str($output)->trim();
if ($output->startsWith('╔')) {
$output = "\n".$output;
@ -165,9 +170,9 @@ private function executeCommandWithProcess($command, $hidden, $customType, $appe
$sanitized_output = sanitize_utf8_text($output);
$new_log_entry = [
'command' => $this->redact_sensitive_info($command),
'command' => $command_hidden ? null : $this->redact_sensitive_info($command),
'output' => $this->redact_sensitive_info($sanitized_output),
'type' => $customType ?? $type === 'err' ? 'stderr' : 'stdout',
'type' => $customType ?? ($type === 'err' ? 'stderr' : 'stdout'),
'timestamp' => Carbon::now('UTC'),
'hidden' => $hidden,
'batch' => static::$batch_counter,