Jail the remote (SSH) terminal path too, not just local
Deploy Ladill Hosting / deploy (push) Successful in 33s

Accounts whose node has an ssh_private_key take the remote branch, which still
launched an unjailed 'runuser' shell — leaving the breach open. Route it through
the same jailkit launcher (no sudo; provider connects as root). Now every
terminal session is chrooted to the customer's own home.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
isaacclad
2026-06-26 23:55:22 +00:00
co-authored by Claude Opus 4.8
parent 2a1228b40e
commit 18d1e735d8
@@ -143,7 +143,10 @@ class RunHostingTerminalWorkerCommand extends Command
throw new \RuntimeException('Unable to establish an SSH connection for the browser terminal.');
}
$remoteCommand = "runuser -u {$account->username} -- bash -lc ".escapeshellarg($launchCommand);
// SECURITY: jail the remote shell too. provider->connect() logs in as
// root, so the launcher runs without sudo and chroots the customer into
// the node's jail (only their own home visible) — never an unjailed shell.
$remoteCommand = $this->buildJailedLaunchCommand($account, $relativePath, false);
return new RemoteSshShellSession($ssh, $remoteCommand, $cols, $rows);
}
@@ -156,17 +159,21 @@ class RunHostingTerminalWorkerCommand extends Command
* /etc, secrets). Invoked via sudo -n, scoped in /etc/sudoers.d/ladill-jailsh
* because the terminal worker runs as www-data.
*/
private function buildJailedLaunchCommand(HostingAccount $account, string $relativePath): string
private function buildJailedLaunchCommand(HostingAccount $account, string $relativePath, bool $sudo = true): string
{
$launcher = (string) config('hosting.terminal_jail_launcher', '/usr/local/sbin/ladill-jailsh');
$rel = $relativePath !== '' ? $relativePath : '/public_html';
return implode(' ', [
'sudo', '-n',
escapeshellarg($launcher),
escapeshellarg($account->username),
escapeshellarg($rel),
]);
$parts = [];
if ($sudo) {
$parts[] = 'sudo';
$parts[] = '-n';
}
$parts[] = escapeshellarg($launcher);
$parts[] = escapeshellarg($account->username);
$parts[] = escapeshellarg($rel);
return implode(' ', $parts);
}
private function buildUserShellBootstrap(HostingAccount $account, string $relativePath, int $cols, int $rows): string