fix(sandbox): prevent shell option interpretation for paths with leading hyphens

Paths starting with "-" (like those containing "---" pattern) can be
interpreted as shell options by the sh shell. This fix adds a helper
function that prepends "./" to paths starting with "-" to prevent
this interpretation.

This fixes the issue where sandbox filesystem operations fail with
"Syntax error: ; unexpected" when file paths contain the "---" pattern
used in auto-generated inbound media filenames like:
file_1095---f00a04a2-99a0-4d98-99b0-dfe61c5a4198.ogg

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Albert Lie
2026-02-24 18:45:48 -05:00
committed by Peter Steinberger
parent b35d00aaf8
commit 5e3502df5f

View File

@@ -96,7 +96,7 @@ class SandboxFsBridgeImpl implements SandboxFsBridge {
const target = this.resolveResolvedPath(params);
await this.assertPathSafety(target, { action: "read files" });
const result = await this.runCommand('set -eu; cat -- "$1"', {
args: [target.containerPath],
args: [ensurePathNotInterpretedAsOption(target.containerPath)],
signal: params.signal,
});
return result.stdout;
@@ -121,7 +121,7 @@ class SandboxFsBridgeImpl implements SandboxFsBridge {
? 'set -eu; cat >"$1"'
: 'set -eu; dir=$(dirname -- "$1"); if [ "$dir" != "." ]; then mkdir -p -- "$dir"; fi; cat >"$1"';
await this.runCommand(script, {
args: [target.containerPath],
args: [ensurePathNotInterpretedAsOption(target.containerPath)],
stdin: buffer,
signal: params.signal,
});
@@ -132,7 +132,7 @@ class SandboxFsBridgeImpl implements SandboxFsBridge {
this.ensureWriteAccess(target, "create directories");
await this.assertPathSafety(target, { action: "create directories", requireWritable: true });
await this.runCommand('set -eu; mkdir -p -- "$1"', {
args: [target.containerPath],
args: [ensurePathNotInterpretedAsOption(target.containerPath)],
signal: params.signal,
});
}
@@ -156,7 +156,7 @@ class SandboxFsBridgeImpl implements SandboxFsBridge {
);
const rmCommand = flags.length > 0 ? `rm ${flags.join(" ")}` : "rm";
await this.runCommand(`set -eu; ${rmCommand} -- "$1"`, {
args: [target.containerPath],
args: [ensurePathNotInterpretedAsOption(target.containerPath)],
signal: params.signal,
});
}
@@ -183,7 +183,7 @@ class SandboxFsBridgeImpl implements SandboxFsBridge {
await this.runCommand(
'set -eu; dir=$(dirname -- "$2"); if [ "$dir" != "." ]; then mkdir -p -- "$dir"; fi; mv -- "$1" "$2"',
{
args: [from.containerPath, to.containerPath],
args: [ensurePathNotInterpretedAsOption(from.containerPath), ensurePathNotInterpretedAsOption(to.containerPath)],
signal: params.signal,
},
);
@@ -197,7 +197,7 @@ class SandboxFsBridgeImpl implements SandboxFsBridge {
const target = this.resolveResolvedPath(params);
await this.assertPathSafety(target, { action: "stat files" });
const result = await this.runCommand('set -eu; stat -c "%F|%s|%Y" -- "$1"', {
args: [target.containerPath],
args: [ensurePathNotInterpretedAsOption(target.containerPath)],
signal: params.signal,
allowFailure: true,
});
@@ -307,7 +307,7 @@ class SandboxFsBridgeImpl implements SandboxFsBridge {
'printf "%s%s\\n" "$canonical" "$suffix"',
].join("\n");
const result = await this.runCommand(script, {
args: [params.containerPath, params.allowFinalSymlink ? "1" : "0"],
args: [ensurePathNotInterpretedAsOption(params.containerPath), params.allowFinalSymlink ? "1" : "0"],
});
const canonical = result.stdout.toString("utf8").trim();
if (!canonical.startsWith("/")) {
@@ -363,6 +363,19 @@ function isPathInsidePosix(root: string, target: string): boolean {
return target === root || target.startsWith(`${root}/`);
}
/**
* Ensure the path is not interpreted as a shell option.
* Paths starting with "-" can be interpreted as command options by the shell.
* Prepend "./" to prevent this interpretation.
*/
function ensurePathNotInterpretedAsOption(path: string): string {
// If path starts with a hyphen (either - or --), prepend ./ to prevent interpretation as option
if (path.startsWith("-") || path.startsWith("--")) {
return "./" + path;
}
return path;
}
async function assertNoHostSymlinkEscape(params: {
absolutePath: string;
rootPath: string;