From e75cdbc13a9b49582e6a49c0cec3c62771a3d4cb Mon Sep 17 00:00:00 2001 From: B <6723574+louisgv@users.noreply.github.com> Date: Sat, 28 Mar 2026 07:25:25 +0000 Subject: [PATCH 1/2] fix(security): pipe SSH command via stdin in digitalocean.sh (#3077) Replace inline variable interpolation of base64-encoded command in the SSH command string with stdin piping, matching the pattern already used by the GCP, Hetzner, and AWS cloud drivers. This eliminates any theoretical injection vector from the encoded_cmd variable being expanded inside the remote shell command. Agent: complexity-hunter Co-Authored-By: Claude Sonnet 4.5 --- sh/e2e/lib/clouds/digitalocean.sh | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/sh/e2e/lib/clouds/digitalocean.sh b/sh/e2e/lib/clouds/digitalocean.sh index 0f0589dc..a5680542 100644 --- a/sh/e2e/lib/clouds/digitalocean.sh +++ b/sh/e2e/lib/clouds/digitalocean.sh @@ -171,24 +171,25 @@ _digitalocean_exec() { return 1 fi - # Base64-encode the command to prevent shell injection when passed as an - # SSH argument. The encoded string contains only [A-Za-z0-9+/=] characters, - # making it safe to embed in single quotes. Stdin is preserved for callers - # that pipe data into cloud_exec. + # Base64-encode the command and pipe it via stdin to avoid any shell + # interpolation on the remote side. This is structurally immune to + # injection regardless of the command content. local encoded_cmd encoded_cmd=$(printf '%s' "${cmd}" | base64 | tr -d '\n') # Validate base64 output contains only safe characters (defense-in-depth). - # Standard base64 only produces [A-Za-z0-9+/=]. This rejects any corruption - # and ensures the value cannot break out of single quotes in the SSH command. + # Standard base64 only produces [A-Za-z0-9+/=]. This rejects any corruption. if ! printf '%s' "${encoded_cmd}" | grep -qE '^[A-Za-z0-9+/=]+$'; then log_err "Invalid base64 encoding of command for SSH exec" return 1 fi - ssh -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null \ + # Pipe the base64 payload via stdin to the remote host. The remote bash + # reads stdin, base64-decodes it, and executes the result. No user-controlled + # data is interpolated into the SSH command string. + printf '%s' "${encoded_cmd}" | ssh -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null \ -o ConnectTimeout=10 -o LogLevel=ERROR -o BatchMode=yes \ - "root@${ip}" "printf '%s' '${encoded_cmd}' | base64 -d | bash" + "root@${ip}" "base64 -d | bash" } # --------------------------------------------------------------------------- From 3e3d69884efa19317b8698e338bac404558c7cdb Mon Sep 17 00:00:00 2001 From: B <6723574+louisgv@users.noreply.github.com> Date: Sat, 28 Mar 2026 15:33:04 +0000 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20revert=20stdin=20piping=20=E2=80=94?= =?UTF-8?q?=20preserve=20caller=20stdin=20for=20cloud=5Fexec?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The stdin piping approach breaks callers that pipe their own data into cloud_exec (e.g. verify.sh:245 pipes attempt numbers via stdin). Restore the original single-quote embedding approach, which is safe because base64 output [A-Za-z0-9+/=] cannot break single quotes. Keep the base64 validation check and improve comments to document the stdin preservation requirement. Agent: security-auditor Co-Authored-By: Claude Sonnet 4.6 --- sh/e2e/lib/clouds/digitalocean.sh | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/sh/e2e/lib/clouds/digitalocean.sh b/sh/e2e/lib/clouds/digitalocean.sh index a5680542..4f9e7b88 100644 --- a/sh/e2e/lib/clouds/digitalocean.sh +++ b/sh/e2e/lib/clouds/digitalocean.sh @@ -171,25 +171,28 @@ _digitalocean_exec() { return 1 fi - # Base64-encode the command and pipe it via stdin to avoid any shell - # interpolation on the remote side. This is structurally immune to - # injection regardless of the command content. + # Base64-encode the command to prevent shell injection when passed as an + # SSH argument. The encoded string contains only [A-Za-z0-9+/=] characters, + # making it safe to embed in single quotes. Stdin is preserved for callers + # that pipe data into cloud_exec (e.g. verify.sh pipes data via stdin). local encoded_cmd encoded_cmd=$(printf '%s' "${cmd}" | base64 | tr -d '\n') # Validate base64 output contains only safe characters (defense-in-depth). - # Standard base64 only produces [A-Za-z0-9+/=]. This rejects any corruption. + # Standard base64 only produces [A-Za-z0-9+/=]. This rejects any corruption + # and ensures the value cannot break out of single quotes in the SSH command. if ! printf '%s' "${encoded_cmd}" | grep -qE '^[A-Za-z0-9+/=]+$'; then log_err "Invalid base64 encoding of command for SSH exec" return 1 fi - # Pipe the base64 payload via stdin to the remote host. The remote bash - # reads stdin, base64-decodes it, and executes the result. No user-controlled - # data is interpolated into the SSH command string. - printf '%s' "${encoded_cmd}" | ssh -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null \ + # Pass the validated base64 payload inside single quotes in the SSH command. + # This is safe because base64 output ([A-Za-z0-9+/=]) cannot contain single + # quotes or any shell metacharacters. Stdin is NOT used here — callers may + # pipe their own data into cloud_exec (see verify.sh:245). + ssh -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null \ -o ConnectTimeout=10 -o LogLevel=ERROR -o BatchMode=yes \ - "root@${ip}" "base64 -d | bash" + "root@${ip}" "printf '%s' '${encoded_cmd}' | base64 -d | bash" } # ---------------------------------------------------------------------------