{"record":{"id":"753e532c60288fc9","repo":"paperclipai/paperclip","slug":"refusing-to-modify-path-not-owned-by-the-current-u","errorCode":null,"errorMessage":"Refusing to modify path not owned by the current user: ${targetPath}.","messagePattern":"Refusing to modify path not owned by the current user: (.+?)\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"cli/src/install-store.ts","lineNumber":53,"sourceCode":"  markerPath: string;\n  lockPath: string;\n  currentPath: string;\n  shimPath: string;\n};\n\nfunction ensurePrivateDirectory(directoryPath: string): void {\n  fs.mkdirSync(directoryPath, { recursive: true, mode: 0o700 });\n  const stat = fs.lstatSync(directoryPath);\n  if (!stat.isDirectory() || stat.isSymbolicLink()) {\n    throw new Error(`Refusing to use non-directory install-store path ${directoryPath}.`);\n  }\n  fs.chmodSync(directoryPath, 0o700);\n}\n\nfunction assertOwnedByCurrentUser(stat: fs.Stats, targetPath: string): void {\n  const getuid = process.getuid;\n  if (typeof getuid === \"function\" && stat.uid !== getuid()) {\n    throw new Error(`Refusing to modify path not owned by the current user: ${targetPath}.`);\n  }\n}\n\nfunction writeFileAtomic(filePath: string, contents: string, mode: number): void {\n  const temporaryPath = path.join(\n    path.dirname(filePath),\n    `.${path.basename(filePath)}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`,\n  );\n  try {\n    fs.writeFileSync(temporaryPath, contents, { mode, flag: \"wx\" });\n    fs.renameSync(temporaryPath, filePath);\n  } finally {\n    fs.rmSync(temporaryPath, { force: true });\n  }\n}\n\nexport function resolveInstallStorePaths(options: {\n  paperclipHome?: string;","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/cli/src/install-store.ts#L35-L71","documentation":"Thrown by assertOwnedByCurrentUser() when a file or directory's stat.uid does not equal process.getuid(). The install store only modifies entries it owns to prevent one user from writing into another user's store (privilege escalation / cross-user tampering). The check is skipped on platforms without process.getuid (e.g. Windows).","triggerScenarios":"Any install-store operation that calls assertOwnedByCurrentUser on a path whose owning uid differs from the current process uid. This includes marker files, cliRoot, shim path, and shell rc files during addManagedPathBlock/removeManagedPathBlock.","commonSituations":"1) Running the CLI under 'sudo' or as root after a prior install was done as a normal user (root's uid 0 != file owner uid). 2) Two distinct OS users sharing a HOME. 3) Files restored from a tarball that preserved a different uid. 4) A container where the mounted volume is owned by a host uid that differs from the container user.","solutions":["Run the CLI as the same user that owns the files ('chown' the store to the current user, or run without sudo).","Fix ownership: 'sudo chown -R $(id -u):$(id -g) ~/.paperclip/cli'.","In containers, ensure the volume mount is chowned to the container user's uid at image/mount time.","Avoid running managed installs as root when the store was created by a non-root user."],"exampleFix":"// before: files owned by uid 1000, running as root (uid 0)\n$ sudo paperclipai install   # throws\n\n// after: chown store to current user, run as that user\n$ sudo chown -R $(id -u):$(id -g) ~/.paperclip/cli\n$ paperclipai install","handlingStrategy":"validation","validationCode":"import fs from \"node:fs\";\n\nfunction ownedByCurrentUser(p: string): boolean {\n  const getuid = process.getuid;\n  if (typeof getuid !== \"function\") return true; // platforms without uid\n  try { return fs.lstatSync(p).uid === getuid(); } catch { return false; }\n}\n\n// Before install ops:\nif (!ownedByCurrentUser(paths.cliRoot)) {\n  console.error(`Run 'sudo chown -R $(id -u):$(id -g) ${paths.cliRoot}'`);\n}","typeGuard":"import fs from \"node:fs\";\n\nfunction isOwnedByCurrentUser(p: string): boolean {\n  const getuid = process.getuid;\n  if (typeof getuid !== \"function\") return true;\n  try { return fs.statSync(p).uid === getuid(); } catch { return false; }\n}","tryCatchPattern":"try {\n  assertManagedInstallStore(paths);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"not owned by the current user\")) {\n    console.error(\"Fix ownership with: sudo chown -R $(id -u):$(id -g) ~/.paperclip/cli\");\n    process.exit(1);\n  }\n  throw err;\n}","preventionTips":["Run the CLI as the same OS user that owns ~/.paperclip.","Avoid 'sudo paperclipai install' unless the entire store is root-owned.","In containers, chown the mounted volume to the container uid.","After restoring from tarball, chown -R to the target user."],"tags":["install-store","security","ownership","filesystem","cli"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}