{"record":{"id":"d99711e7e82d0f9d","repo":"paperclipai/paperclip","slug":"refusing-to-write-export-file-outside-output-direc","errorCode":null,"errorMessage":"Refusing to write export file outside output directory: ${relativePath}","messagePattern":"Refusing to write export file outside output directory: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"cli/src/commands/client/company.ts","lineNumber":1230,"sourceCode":"  for (const [relativePath, content] of Object.entries(exported.files)) {\n    const normalized = relativePath.replace(/\\\\/g, \"/\");\n    const filePath = resolveExportOutputPath(root, normalized);\n    await mkdir(path.dirname(filePath), { recursive: true });\n    const writeValue = portableFileEntryToWriteValue(content);\n    if (typeof writeValue === \"string\") {\n      await writeFile(filePath, writeValue, \"utf8\");\n    } else {\n      await writeFile(filePath, writeValue);\n    }\n  }\n}\n\nexport function resolveExportOutputPath(root: string, relativePath: string): string {\n  const resolvedRoot = path.resolve(root);\n  const filePath = path.resolve(resolvedRoot, relativePath);\n  const rootPrefix = resolvedRoot.endsWith(path.sep) ? resolvedRoot : `${resolvedRoot}${path.sep}`;\n  if (filePath !== resolvedRoot && !filePath.startsWith(rootPrefix)) {\n    throw new Error(`Refusing to write export file outside output directory: ${relativePath}`);\n  }\n  return filePath;\n}\n\nexport async function confirmOverwriteExportDirectory(\n  outDir: string,\n  opts: { force?: boolean } = {},\n): Promise<void> {\n  const root = path.resolve(outDir);\n  const stats = await stat(root).catch(() => null);\n  if (!stats) return;\n  if (!stats.isDirectory()) {\n    throw new Error(`Export output path ${root} exists and is not a directory.`);\n  }\n\n  const entries = await readdir(root);\n  if (entries.length === 0) return;\n","sourceCodeStart":1212,"sourceCodeEnd":1248,"githubUrl":"https://github.com/paperclipai/paperclip/blob/120ae5428fa29bee300bcf806491cd4d965fbb7c/cli/src/commands/client/company.ts#L1212-L1248","documentation":"resolveExportOutputPath is a path-traversal guard: after resolving root and the archive-relative entry path, the target no longer sits under the output root, so writing it would escape the export directory.","triggerScenarios":"Thrown at cli/src/commands/client/company.ts:1230 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Fix the export package so all entries resolve inside the chosen output directory; do not hand-craft paths with '..' segments."],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"120ae5428fa29bee300bcf806491cd4d965fbb7c","analyzedAt":"2026-08-18T22:49:45.177Z","contentChangedAt":"2026-08-18T22:49:45.177Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}