{"record":{"id":"50c9f1f02424a622","repo":"paperclipai/paperclip","slug":"post-upload-command-cwd-is-not-a-confined-absolute","errorCode":null,"errorMessage":"post-upload command cwd is not a confined absolute POSIX path: ${raw}","messagePattern":"post-upload command cwd is not a confined absolute POSIX path: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/adapter-utils/src/command-managed-runtime.ts","lineNumber":187,"sourceCode":"/**\n * Host-side confinement guard for a sync operation's post-upload command `cwd`\n * (Security Condition C2). Runs BEFORE any handoff — native delegation OR the\n * generic fallback — so an out-of-root `cwd` is rejected fail-closed before a\n * provider ever sees it. `cwd` (when present) MUST be an absolute POSIX path with\n * no `..` segment, confined to (equal to or under) one of the operation's own\n * file-mapping target paths. Commands with no `cwd` are unconstrained here and\n * default to the runtime's stable command cwd at exec time.\n */\nexport function assertPostUploadCommandsConfined(operations: readonly SandboxSyncOperation[]): void {\n  for (const operation of operations) {\n    const commands = operation.postUploadCommands ?? [];\n    if (commands.length === 0) continue;\n    const targetRoots = operation.files.map((mapping) => path.posix.normalize(mapping.targetPath));\n    for (const command of commands) {\n      if (command.cwd == null) continue;\n      const raw = command.cwd;\n      if (!path.posix.isAbsolute(raw) || raw.split(\"/\").includes(\"..\")) {\n        throw new Error(`post-upload command cwd is not a confined absolute POSIX path: ${raw}`);\n      }\n      const normalized = path.posix.normalize(raw);\n      const within = targetRoots.some(\n        (root) => normalized === root || normalized.startsWith(`${root}/`),\n      );\n      if (!within) {\n        throw new Error(`post-upload command cwd escapes the operation's target root: ${raw}`);\n      }\n    }\n  }\n}\n\nexport function createCommandManagedRuntimeClient(input: {\n  runner: CommandManagedRuntimeRunner;\n  commandCwd: string;\n  timeoutMs: number;\n  shellCommand?: \"bash\" | \"sh\" | null;\n}): SandboxManagedRuntimeClient {","sourceCodeStart":169,"sourceCodeEnd":205,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/packages/adapter-utils/src/command-managed-runtime.ts#L169-L205","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Change the cwd to an absolute POSIX path with no '..' segments that matches or is under one of the operation's file-mapping targetPaths.","If you intended a relative path, compute the absolute path from the targetPath on the host before constructing the operation.","Remove the cwd property entirely if the command should run from the runtime's default command cwd (which defaults to '/').","Validate all postUploadCommands cwd values against path.posix.isAbsolute and ensure no '..' segments."],"exampleFix":"// before: relative cwd with '..'\nconst ops: SandboxSyncOperation[] = [{\n  files: [{ kind: \"directory\", sourcePath: \"./app\", targetPath: \"/workspace/app\" }],\n  postUploadCommands: [{ command: \"make build\", cwd: \"../build\" }],\n}];\n\n// after: absolute POSIX path confined under targetPath\nconst ops: SandboxSyncOperation[] = [{\n  files: [{ kind: \"directory\", sourcePath: \"./app\", targetPath: \"/workspace/app\" }],\n  postUploadCommands: [{ command: \"make build\", cwd: \"/workspace/app\" }],\n}];","handlingStrategy":"validation","validationCode":"import path from 'node:path';\n\nfunction arePostUploadCommandsConfined(operations: readonly SandboxSyncOperation[]): boolean {\n  for (const op of operations) {\n    const commands = op.postUploadCommands ?? [];\n    if (commands.length === 0) continue;\n    const targetRoots = op.files.map((m) => path.posix.normalize(m.targetPath));\n    for (const cmd of commands) {\n      if (cmd.cwd == null) continue;\n      if (!path.posix.isAbsolute(cmd.cwd) || cmd.cwd.split('/').includes('..')) return false;\n      const normalized = path.posix.normalize(cmd.cwd);\n      const within = targetRoots.some((root) => normalized === root || normalized.startsWith(`${root}/`));\n      if (!within) return false;\n    }\n  }\n  return true;\n}\n\n// Call before client.syncIn(operations):\nif (!arePostUploadCommandsConfined(operations)) {\n  throw new Error('Post-upload command cwd validation failed; fix paths before sync.');\n}","typeGuard":"function isConfinedPostUploadCommand(\n  command: { cwd?: string | null },\n  targetRoots: string[],\n): boolean {\n  if (command.cwd == null) return true;\n  if (!path.posix.isAbsolute(command.cwd) || command.cwd.split('/').includes('..')) return false;\n  const normalized = path.posix.normalize(command.cwd);\n  return targetRoots.some((root) => normalized === root || normalized.startsWith(`${root}/`));\n}","tryCatchPattern":"try {\n  await client.syncIn(operations);\n} catch (error) {\n  if (error instanceof Error && error.message.includes('not a confined absolute POSIX path')) {\n    // Fix the cwd to be absolute with no '..' segments\n    console.error('Post-upload cwd must be absolute POSIX with no .. segments:', error.message);\n  }\n  throw error;\n}","preventionTips":["Always use absolute POSIX paths for postUploadCommands cwd—never relative paths.","Ensure the cwd is equal to or a subdirectory of one of the operation's file-mapping targetPaths.","Remove the cwd property entirely if the command should run from the runtime default cwd.","Call assertPostUploadCommandsConfined explicitly in tests to validate operations before runtime."],"tags":["security","sandbox","path-confinement","adapter-utils","posix"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}