{"record":{"id":"5138b06be28e74b4","repo":"paperclipai/paperclip","slug":"createos-transfer-requires-a-confined-absolute-sandbox-path","errorCode":null,"errorMessage":"CreateOS transfer requires a confined absolute sandbox path.","messagePattern":"CreateOS transfer requires a confined absolute sandbox path\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/plugins/sandbox-providers/createos/src/file-sync.ts","lineNumber":16,"sourceCode":"import path from \"node:path\";\nimport os from \"node:os\";\nimport { randomUUID } from \"node:crypto\";\nimport { createReadStream, createWriteStream, promises as fs } from \"node:fs\";\nimport { Readable } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\";\nimport * as tar from \"tar\";\nimport type { PluginEnvironmentSyncInParams, PluginEnvironmentSyncResult } from \"@paperclipai/plugin-sdk\";\nimport { CreateosClient, identifier } from \"./client.js\";\nimport { execute, shellQuote } from \"./execute.js\";\n\nconst ROOT = \"/paperclip-workspace\";\n\nexport function assertRemotePath(value: string): void {\n  if (!path.posix.isAbsolute(value) || value.includes(\"\\0\") || value.split(\"/\").includes(\"..\")) {\n    throw new Error(\"CreateOS transfer requires a confined absolute sandbox path.\");\n  }\n  const normalized = path.posix.normalize(value);\n  if (normalized !== ROOT && !normalized.startsWith(`${ROOT}/`)) throw new Error(\"CreateOS transfer path escapes the workspace.\");\n}\n\nfunction remoteGuard(candidate: string): string {\n  assertRemotePath(candidate);\n  // Re-check symlinks inside the sandbox immediately before use. A missing\n  // canonicalizer fails the command rather than weakening containment.\n  return `root=$(realpath -- ${shellQuote(ROOT)}) && test \"$root\" = ${shellQuote(ROOT)} && ` +\n    `resolved=$(realpath -m -- ${shellQuote(candidate)}) && ` +\n    `case \"$resolved\" in \"$root\"|\"$root\"/*) ;; *) exit 1 ;; esac`;\n}\n\nexport async function validateArchive(file: string): Promise<number> {\n  let invalid = false;\n  let bytes = 0;\n  let files = 0;","sourceCodeStart":1,"sourceCodeEnd":34,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/packages/plugins/sandbox-providers/createos/src/file-sync.ts#L1-L34","documentation":"assertRemotePath validates every remote path used in CreateOS file transfers. A path must be POSIX-absolute, contain no NUL byte, and no '..' segment; otherwise it cannot be confined and the transfer is refused. This is the first-line containment check before path normalization.","triggerScenarios":"Calling syncFiles/run/remoteGuard with a relative path (e.g. 'foo/bar'), a Windows-style path ('C:\\\\foo'), a path containing a NUL character, or a segment equal to '..' (e.g. '/paperclip-workspace/../etc').","commonSituations":"Joining local relative paths and passing them as remote paths by mistake; user-supplied destination paths not normalized; interpolating OS-specific paths on a Windows host into a POSIX sandbox.","solutions":["Convert the remote path to an absolute POSIX path rooted at /paperclip-workspace before calling the transfer API.","Normalize the path with path.posix.normalize and strip '..' segments yourself before submission.","Never pass raw user input as the remote path; map it under ROOT with path.posix.join(ROOT, relativePart).","On Windows hosts, replace backslashes with forward slashes and drop drive letters before use."],"exampleFix":"// before\nawait syncFiles(lease, [{ local: file, remote: \"output/result.json\" }]);\n// after\nconst remote = path.posix.join(\"/paperclip-workspace\", \"output/result.json\");\nawait syncFiles(lease, [{ local: file, remote }]);","handlingStrategy":"validation","validationCode":"function toSafeRemotePath(p) {\n  const posix = String(p).replaceAll(\"\\\\\", \"/\");\n  const normalized = path.posix.normalize(posix);\n  if (!path.posix.isAbsolute(normalized) || normalized.includes(\"\\0\")) throw new Error(\"remote path must be absolute\");\n  return path.posix.join(\"/paperclip-workspace\", path.posix.relative(\"/paperclip-workspace\", normalized) || \"\");\n}","typeGuard":"function isConfinedRemotePath(p) {\n  const n = path.posix.normalize(p);\n  return path.posix.isAbsolute(n) && !n.includes(\"\\0\") && (n === \"/paperclip-workspace\" || n.startsWith(\"/paperclip-workspace/\"));\n}","tryCatchPattern":"try {\n  await syncFiles(lease, transfers);\n} catch (e) {\n  if (e.message.includes(\"confined absolute sandbox path\")) {\n    log.error(\"bad remote path\", { transfers });\n  }\n  throw e;\n}","preventionTips":["Always build remote paths with path.posix.join('/paperclip-workspace', rel).","Never pass raw user input or host-relative paths as remote paths.","Convert Windows paths (backslashes, drive letters) before transfer calls."],"tags":["path-validation","security","file-transfer","sandbox"],"backgroundTag":"invalid-argument-format","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-22T16:17:23.217Z"}