From e13d809f377ff9a8bfedf4bafe2887fca8eaeec1 Mon Sep 17 00:00:00 2001 From: A <258483684+la14-1@users.noreply.github.com> Date: Fri, 27 Feb 2026 03:20:32 -0800 Subject: [PATCH] fix(security): add path traversal defense-in-depth to uploadFile (#1988) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add `|| remotePath.includes("..")` check to hetzner, digitalocean, and aws uploadFile functions. The regex `/^[a-zA-Z0-9/_.~-]+$/` allows `.` characters, so paths like `../../etc/passwd` pass the regex but are path traversal attempts. gcp, daytona, and sprite already include this explicit check — this makes all providers consistent. Agent: code-health Co-authored-by: B <6723574+louisgv@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.5 --- packages/cli/src/aws/aws.ts | 2 +- packages/cli/src/digitalocean/digitalocean.ts | 2 +- packages/cli/src/hetzner/hetzner.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/aws/aws.ts b/packages/cli/src/aws/aws.ts index cde6ade6..9ff14ab3 100644 --- a/packages/cli/src/aws/aws.ts +++ b/packages/cli/src/aws/aws.ts @@ -1086,7 +1086,7 @@ export async function runServerCapture(cmd: string, timeoutSecs?: number): Promi } export async function uploadFile(localPath: string, remotePath: string): Promise { - if (!/^[a-zA-Z0-9/_.~-]+$/.test(remotePath)) { + if (!/^[a-zA-Z0-9/_.~-]+$/.test(remotePath) || remotePath.includes("..")) { throw new Error(`Invalid remote path: ${remotePath}`); } const keyOpts = getSshKeyOpts(await ensureSshKeys()); diff --git a/packages/cli/src/digitalocean/digitalocean.ts b/packages/cli/src/digitalocean/digitalocean.ts index 14479f43..4b0ac4c5 100644 --- a/packages/cli/src/digitalocean/digitalocean.ts +++ b/packages/cli/src/digitalocean/digitalocean.ts @@ -1040,7 +1040,7 @@ export async function runServerCapture(cmd: string, timeoutSecs?: number, ip?: s export async function uploadFile(localPath: string, remotePath: string, ip?: string): Promise { const serverIp = ip || doServerIp; - if (!/^[a-zA-Z0-9/_.~-]+$/.test(remotePath)) { + if (!/^[a-zA-Z0-9/_.~-]+$/.test(remotePath) || remotePath.includes("..")) { logError(`Invalid remote path: ${remotePath}`); throw new Error("Invalid remote path"); } diff --git a/packages/cli/src/hetzner/hetzner.ts b/packages/cli/src/hetzner/hetzner.ts index c086a0ab..e7b745b5 100644 --- a/packages/cli/src/hetzner/hetzner.ts +++ b/packages/cli/src/hetzner/hetzner.ts @@ -579,7 +579,7 @@ export async function runServerCapture(cmd: string, timeoutSecs?: number, ip?: s export async function uploadFile(localPath: string, remotePath: string, ip?: string): Promise { const serverIp = ip || hetznerServerIp; - if (!/^[a-zA-Z0-9/_.~-]+$/.test(remotePath)) { + if (!/^[a-zA-Z0-9/_.~-]+$/.test(remotePath) || remotePath.includes("..")) { logError(`Invalid remote path: ${remotePath}`); throw new Error("Invalid remote path"); }