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

  1. 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').
  2. If the command needs to run in a parent directory, add a file mapping for that directory so it becomes a valid target root.
  3. Remove the cwd property to use the runtime's default command cwd instead.
  4. 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

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


AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12). Data as JSON: /api/errors/3ca28f2688881cd7. Report an issue: GitHub.