paperclipai/paperclip · error · Error

post-upload command cwd is not a confined absolute POSIX pat

Error message

post-upload command cwd is not a confined absolute POSIX path: ${raw}

What it means

Thrown by assertPostUploadCommandsConfined (Security Condition C2) when a post-upload command's cwd is not an absolute POSIX path or contains a '..' segment. This is a fail-closed host-side validation that runs before any sync handoff (native or fallback). The cwd must be absolute because relative paths would resolve against the runtime's stable cwd, not the operation's target, and '..' segments could escape the intended confinement root.

Source

Thrown at packages/adapter-utils/src/command-managed-runtime.ts:187

/**
 * Host-side confinement guard for a sync operation's post-upload command `cwd`
 * (Security Condition C2). Runs BEFORE any handoff — native delegation OR the
 * generic fallback — so an out-of-root `cwd` is rejected fail-closed before a
 * provider ever sees it. `cwd` (when present) MUST be an absolute POSIX path with
 * no `..` segment, confined to (equal to or under) one of the operation's own
 * file-mapping target paths. Commands with no `cwd` are unconstrained here and
 * 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 {

View on GitHub (pinned to 67001ec6eb)

Solutions

  1. Change the cwd to an absolute POSIX path with no '..' segments that matches or is under one of the operation's file-mapping targetPaths.
  2. If you intended a relative path, compute the absolute path from the targetPath on the host before constructing the operation.
  3. Remove the cwd property entirely if the command should run from the runtime's default command cwd (which defaults to '/').
  4. Validate all postUploadCommands cwd values against path.posix.isAbsolute and ensure no '..' segments.

Example fix

// before: relative cwd with '..'
const ops: SandboxSyncOperation[] = [{
  files: [{ kind: "directory", sourcePath: "./app", targetPath: "/workspace/app" }],
  postUploadCommands: [{ command: "make build", cwd: "../build" }],
}];

// after: absolute POSIX path confined under targetPath
const ops: SandboxSyncOperation[] = [{
  files: [{ kind: "directory", sourcePath: "./app", targetPath: "/workspace/app" }],
  postUploadCommands: [{ command: "make build", cwd: "/workspace/app" }],
}];
Defensive patterns

Strategy: validation

Validate before calling

import path from 'node:path';

function arePostUploadCommandsConfined(operations: readonly SandboxSyncOperation[]): boolean {
  for (const op of operations) {
    const commands = op.postUploadCommands ?? [];
    if (commands.length === 0) continue;
    const targetRoots = op.files.map((m) => path.posix.normalize(m.targetPath));
    for (const cmd of commands) {
      if (cmd.cwd == null) continue;
      if (!path.posix.isAbsolute(cmd.cwd) || cmd.cwd.split('/').includes('..')) return false;
      const normalized = path.posix.normalize(cmd.cwd);
      const within = targetRoots.some((root) => normalized === root || normalized.startsWith(`${root}/`));
      if (!within) return false;
    }
  }
  return true;
}

// Call before client.syncIn(operations):
if (!arePostUploadCommandsConfined(operations)) {
  throw new Error('Post-upload command cwd validation failed; fix paths before sync.');
}

Type guard

function isConfinedPostUploadCommand(
  command: { cwd?: string | null },
  targetRoots: string[],
): boolean {
  if (command.cwd == null) return true;
  if (!path.posix.isAbsolute(command.cwd) || command.cwd.split('/').includes('..')) return false;
  const normalized = path.posix.normalize(command.cwd);
  return targetRoots.some((root) => normalized === root || normalized.startsWith(`${root}/`));
}

Try / catch

try {
  await client.syncIn(operations);
} catch (error) {
  if (error instanceof Error && error.message.includes('not a confined absolute POSIX path')) {
    // Fix the cwd to be absolute with no '..' segments
    console.error('Post-upload cwd must be absolute POSIX with no .. segments:', error.message);
  }
  throw error;
}

Prevention

When it happens

Trigger: Calling client.syncIn(operations) or assertPostUploadCommandsConfined(operations) directly, where any operation has a postUploadCommands entry with a cwd that is relative (e.g., './build'), contains '..' (e.g., '/workspace/../etc'), or is not a POSIX-style absolute path.

Common situations: Configuring post-upload commands with relative cwd values assuming they resolve relative to the target path. Using '..' in the cwd to reference sibling directories. Windows-style absolute paths (C:\...) on a POSIX sandbox. Accidental empty string or malformed path.

Related errors


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