{"record":{"id":"50b6b0bf6bc96b1d","repo":"JuliusBrussee/caveman","slug":"caveman-build-imported-source-escapes-project-roo","errorCode":null,"errorMessage":"caveman build: imported source escapes project root","messagePattern":"caveman build: imported source escapes project root","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/source-graph.ts","lineNumber":98,"sourceCode":"          if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n        }\n      }\n      if (!found) {\n        throw new Error(`caveman build: unresolved relative source import ${JSON.stringify(specifier)}`);\n      }\n      if (bare) {\n        await collectPackageClosure(packageRoot!, files, packageRoots);\n        continue;\n      }\n      const lexicalFound = found;\n      found = await realpath(found);\n      const insideRoot = isPathWithin(canonicalRoot, found);\n      if (!insideRoot && !bare && !external.has(path)) {\n        const lexicalInsideRoot = isPathWithin(canonicalRoot, lexicalFound);\n        if (lexicalInsideRoot) {\n          throw new Error(\"caveman build: source graph symlink escapes project root\");\n        }\n        throw new Error(\"caveman build: imported source escapes project root\");\n      }\n      if (!insideRoot) external.add(found);\n      if (!files.has(found)) {\n        files.add(found);\n        if (!traversalStopRoots.some((stop) => isPathWithin(stop, found))) {\n          queue.push(found);\n        }\n      }\n    }\n  }\n  return files;\n}\n\nfunction explicitImportCandidates(base: string, importer: string): string[] {\n  const importerExtension = extname(importer);\n  if (![\".ts\", \".tsx\", \".mts\", \".cts\"].includes(importerExtension)) return [base];\n  const extension = extname(base);\n  const stem = base.slice(0, -extension.length);","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/agent/src/source-graph.ts#L80-L116","documentation":"When a resolved import's realpath is outside the canonical project root and the lexical path was also outside (i.e. not a symlink escape), the walker refuses to include the file in the build graph with this error. It guards against the source graph silently pulling in arbitrary files from elsewhere on disk.","triggerScenarios":"expandSourceGraph follows a non-bare import whose resolved location (already outside root) is not registered as external, and the lexical path also lies outside canonicalRoot.","commonSituations":"Importing an absolute path outside the repo (e.g. '../../..../other-project/file.ts'); IDE auto-imports grabbing files from open workspace folders; generated imports pointing at out-of-repo artifacts.","solutions":["Move the imported file into the project or convert it to a proper package dependency (bare import)","Remove/fix the out-of-root absolute or ../-heavy import path","If the external location is legitimate, add it to the allowed external set so it's treated as a boundary instead of graph content","Check for editor auto-import misconfiguration that inserts absolute out-of-repo paths"],"exampleFix":"// before\nimport { config } from '/Users/dev/shared/config.ts';\n// after\nimport { config } from './config';","handlingStrategy":"validation","validationCode":"import { resolve } from 'path';\nconst target = resolve(dirname(importerPath), specifier);\nif (!target.startsWith(canonicalRoot + '/')) throw new Error(`import escapes root: ${specifier}`);","typeGuard":null,"tryCatchPattern":"try {\n  await build({ files: sourceFiles(entry) });\n} catch (e) {\n  if (e.message.includes('imported source escapes project root')) {\n    console.error('Convert the import to an in-repo file or npm dependency');\n  } else throw e;\n}","preventionTips":["Disable editor auto-imports that insert absolute out-of-repo paths","Keep all source inside the project root; externalize via package.json deps","Review ../-heavy imports in code review"],"tags":["build","security","filesystem","imports"],"backgroundTag":"path-traversal-blocked","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}