mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
fix(sudo): do not add sudo to pipes inside quoted strings
Pipes in single or double quotes (Go templates, sh -c scripts) are text, so the non-root sudo parser now leaves them unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
43b7405c4c
commit
f3af1c9197
@@ -39,6 +39,41 @@ function isSingleSudoShellScript(string $line): bool
|
||||
return preg_match("/^\\s*sudo (?:ba)?sh -c '(?:[^']|'\\\\'')*'\\s*$/", $line) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts sudo after each ` | ` that the shell sees as a pipe. Pipes inside single or double quotes
|
||||
* (a Go template, an `sh -c "..."` script) are text and stay unchanged; a stage that already
|
||||
* starts with sudo is not given a second one.
|
||||
*/
|
||||
function addSudoAfterUnquotedPipes(string $line): string
|
||||
{
|
||||
$result = '';
|
||||
$quote = null;
|
||||
$length = strlen($line);
|
||||
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$char = $line[$i];
|
||||
|
||||
if ($quote === null && substr($line, $i, 3) === ' | ') {
|
||||
$result .= str_starts_with(substr($line, $i + 3), 'sudo ') ? ' | ' : ' | sudo ';
|
||||
$i += 2;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$result .= $char;
|
||||
|
||||
if ($char === '\\' && $quote !== "'" && $i + 1 < $length) {
|
||||
$result .= $line[++$i];
|
||||
} elseif ($quote === null && ($char === "'" || $char === '"')) {
|
||||
$quote = $char;
|
||||
} elseif ($char === $quote) {
|
||||
$quote = null;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
function parseCommandsByLineForSudo(Collection $commands, Server $server): array
|
||||
{
|
||||
$commands = $commands->map(function ($line) {
|
||||
@@ -148,8 +183,8 @@ function parseCommandsByLineForSudo(Collection $commands, Server $server): array
|
||||
$line = $line->replace('&&', '&& sudo');
|
||||
}
|
||||
// Don't insert sudo into pipes for complex commands
|
||||
if (! $isComplexPipeCommand && str($line)->contains(' | ')) {
|
||||
$line = $line->replace(' | ', ' | sudo ');
|
||||
if (! $isComplexPipeCommand) {
|
||||
$line = str(addSudoAfterUnquotedPipes($line->value()));
|
||||
}
|
||||
|
||||
return $line->value();
|
||||
@@ -182,7 +217,7 @@ function parseLineForSudo(string $command, Server $server): string
|
||||
$command = str($command)->replace('&&', '&& sudo ')->value();
|
||||
}
|
||||
// Each pipe stage is a separate process; without sudo, `| tee file` writes as the SSH user.
|
||||
$command = preg_replace('/ \| (?!sudo )/', ' | sudo ', $command);
|
||||
$command = addSudoAfterUnquotedPipes($command);
|
||||
|
||||
return $command;
|
||||
}
|
||||
|
||||
@@ -798,3 +798,32 @@ test('parseLineForSudo adds sudo to each pipe stage without doubling it', functi
|
||||
->and(parseLineForSudo('docker ps | sudo grep app', $this->server))
|
||||
->toBe('sudo docker ps | sudo grep app');
|
||||
});
|
||||
|
||||
test('parseLineForSudo keeps pipes inside quoted strings unchanged', function (string $line, string $expected) {
|
||||
expect(parseLineForSudo($line, $this->server))->toBe($expected);
|
||||
})->with([
|
||||
'single-quoted go template' => [
|
||||
"docker inspect --format '{{a | b}}' coolify-proxy | grep x",
|
||||
"sudo docker inspect --format '{{a | b}}' coolify-proxy | sudo grep x",
|
||||
],
|
||||
'double-quoted sh -c script' => [
|
||||
'sh -c "x | y"',
|
||||
'sudo sh -c "x | y"',
|
||||
],
|
||||
'escaped double quote inside double quotes' => [
|
||||
'echo "a \" | b" | tee /tmp/coolify/file',
|
||||
'sudo echo "a \" | b" | sudo tee /tmp/coolify/file',
|
||||
],
|
||||
'single quote inside double quotes' => [
|
||||
"echo \"it's | here\" | tee /tmp/coolify/file",
|
||||
"sudo echo \"it's | here\" | sudo tee /tmp/coolify/file",
|
||||
],
|
||||
]);
|
||||
|
||||
test('parseCommandsByLineForSudo keeps pipes inside quoted strings unchanged', function () {
|
||||
$result = parseCommandsByLineForSudo(collect([
|
||||
"docker ps --format '{{.Names}} | {{.Image}}' | grep app",
|
||||
]), $this->server);
|
||||
|
||||
expect($result[0])->toBe("sudo docker ps --format '{{.Names}} | {{.Image}}' | sudo grep app");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user