paperclipai/paperclip · error · Error
post-upload command cwd escapes the operation's target root:
Error message
post-upload command cwd escapes the operation's target root: ${raw} What it means
Thrown by assertPostUploadCommandsConfined (Security Condition C2) when a post-upload command's cwd is an absolute POSIX path with no '..' but does not equal and is not a subdirectory of any of the operation's file-mapping targetPaths. After normalizing both the cwd and all targetPaths, the check requires the cwd to start with '<root>/' or exactly equal a root. This prevents commands from running in arbitrary sandbox directories outside the uploaded file set.
Source
Thrown at packages/adapter-utils/src/command-managed-runtime.ts:194
* default to the runtime's stable command cwd at exec time.
*/
export function assertPostUploadCommandsConfined(operations: readonly SandboxSyncOperation[]): void {
for (const operation of operations) {
const commands = operation.postUploadCommands ?? [];
if (commands.length === 0) continue;
const targetRoots = operation.files.map((mapping) => path.posix.normalize(mapping.targetPath));
for (const command of commands) {
if (command.cwd == null) continue;
const raw = command.cwd;
if (!path.posix.isAbsolute(raw) || raw.split("/").includes("..")) {
throw new Error(`post-upload command cwd is not a confined absolute POSIX path: ${raw}`);
}
const normalized = path.posix.normalize(raw);
const within = targetRoots.some(
(root) => normalized === root || normalized.startsWith(`${root}/`),
);
if (!within) {
throw new Error(`post-upload command cwd escapes the operation's target root: ${raw}`);
}
}
}
}
export function createCommandManagedRuntimeClient(input: {
runner: CommandManagedRuntimeRunner;
commandCwd: string;
timeoutMs: number;
shellCommand?: "bash" | "sh" | null;
}): SandboxManagedRuntimeClient {
const shellCommand = preferredShellForSandbox(input.shellCommand);
const runShell = async (
script: string,
opts: {
stdin?: string;
timeoutMs?: number;
onLog?: (stream: "stdout" | "stderr", chunk: string) => Promise<void>;View on GitHub (pinned to 67001ec6eb)
Solutions
- Ensure the cwd is exactly one of the targetPaths or a subdirectory of one (e.g., if targetPath is '/workspace/app', cwd can be '/workspace/app' or '/workspace/app/subdir').
- If the command needs to run in a parent directory, add a file mapping for that directory so it becomes a valid target root.
- Remove the cwd property to use the runtime's default command cwd instead.
- Review all file mappings in the operation and align the cwd to one of their targetPaths.
Example fix
// before: cwd is a sibling of the targetPath
const ops: SandboxSyncOperation[] = [{
files: [{ kind: "directory", sourcePath: "./app", targetPath: "/workspace/app" }],
postUploadCommands: [{ command: "npm run build", cwd: "/workspace/scripts" }],
}];
// after: cwd confined under targetPath
const ops: SandboxSyncOperation[] = [{
files: [{ kind: "directory", sourcePath: "./app", targetPath: "/workspace/app" }],
postUploadCommands: [{ command: "npm run build", cwd: "/workspace/app" }],
}]; Defensive patterns
Strategy: validation
Validate before calling
import path from 'node:path';
function isCwdWithinTargetRoots(cwd: string, targetRoots: string[]): boolean {
const normalized = path.posix.normalize(cwd);
return targetRoots.some((root) => normalized === root || normalized.startsWith(`${root}/`));
}
// Call before client.syncIn:
for (const op of operations) {
const targetRoots = op.files.map((m) => path.posix.normalize(m.targetPath));
for (const cmd of op.postUploadCommands ?? []) {
if (cmd.cwd && !isCwdWithinTargetRoots(cmd.cwd, targetRoots)) {
throw new Error(`cwd ${cmd.cwd} must be within one of: ${targetRoots.join(', ')}`);
}
}
} Try / catch
try {
await client.syncIn(operations);
} catch (error) {
if (error instanceof Error && error.message.includes('escapes the operation')) {
// Align the cwd to be within a targetPath
console.error('Post-upload cwd must be within a file-mapping targetPath:', error.message);
}
throw error;
} Prevention
- Map every directory a post-upload command needs to operate in as a file mapping targetPath in the same operation.
- The cwd cannot be a parent of the targetPath—only the root itself or a subdirectory beneath it.
- If a command needs to run in a shared/parent directory, either omit cwd (use default '/') or add a file mapping for that directory.
- Test operations with assertPostUploadCommandsConfined before deploying to catch confinement violations early.
When it happens
Trigger: Calling client.syncIn(operations) where a postUploadCommand has a valid absolute cwd like '/etc' or '/tmp' that is not under any of the operation's file-mapping targetPath values. For example, files map to '/workspace/app' but the command cwd is '/workspace/other'.
Common situations: Configuring post-upload commands that operate in a different directory than the uploaded files. Typing a targetPath that doesn't exactly match the cwd prefix. Expecting the cwd to be a parent of the targetPath (the check only allows the cwd to be the root or under it, not above it).
Related errors
- post-upload command cwd is not a confined absolute POSIX pat
- networkAllowlist[${index}] must use an exact hostname; wildc
- ${action} failed with exit code ${result.exitCode ?? "null"}
- Could not determine remote file size for ${remotePath}
- Remote file read was truncated for ${remotePath}: ${out.byte
AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12).
Data as JSON: /api/errors/3ca28f2688881cd7.
Report an issue: GitHub.