{"record":{"id":"06366d76b388eb4f","repo":"JuliusBrussee/caveman","slug":"package-export-escapes-package-root","errorCode":null,"errorMessage":"package export escapes package root","messagePattern":"package export escapes package root","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/source-graph.ts","lineNumber":141,"sourceCode":"  return [base];\n}\n\nasync function resolveImportOnlyDependency(specifier: string, importer: string): Promise<string> {\n  const packageName = barePackageName(specifier);\n  const packageJSONPath = resolvePackageJSON(packageName, importer);\n  const packageJSON = JSON.parse(await readFile(packageJSONPath, \"utf8\")) as {\n    exports?: unknown;\n  };\n  const subpath = specifier === packageName ? \".\" : `.${specifier.slice(packageName.length)}`;\n  const target = resolvePackageExport(packageJSON.exports, subpath);\n  if (target === undefined || !target.startsWith(\"./\")) {\n    throw new Error(\"import export not found\");\n  }\n  const packageRoot = dirname(packageJSONPath);\n  const absolute = resolve(packageRoot, target);\n  const relativeTarget = relative(packageRoot, absolute);\n  if (relativeTarget === \"..\" || relativeTarget.startsWith(\"../\") || relativeTarget.startsWith(\"..\\\\\")) {\n    throw new Error(\"package export escapes package root\");\n  }\n  return absolute;\n}\n\nfunction dependencyPackageRoot(specifier: string, importer: string): string {\n  return dirname(resolvePackageJSON(barePackageName(specifier), importer));\n}\n\nfunction resolvePackageJSON(packageName: string, importer: string): string {\n  const packageJSONPath = findPackageJSON(packageName, pathToFileURL(importer));\n  if (packageJSONPath === undefined) throw new Error(\"package not found\");\n  return packageJSONPath;\n}\n\nasync function collectPackageClosure(\n  packageRoot: string,\n  files: Set<string>,\n  visitedRoots: Set<string>,","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/agent/src/source-graph.ts#L123-L159","documentation":"Thrown by resolveImportOnlyDependency when the resolved export target, after resolving against the package root, points outside that root (relative path starts with \"..\" or \"..\\\\\"). This is a path-traversal guard: a package.json exports entry must not escape its own package directory. The library rejects such entries rather than following them.","triggerScenarios":"expandSourceGraph resolves an import; the package's exports entry for the subpath contains a target like \"../shared/lib.js\" or an absolute path outside packageRoot, so relative(packageRoot, absolute) escapes upward.","commonSituations":"A hand-edited or generated package.json with an exports target referencing files outside the package (monorepo symlinking mistakes, build scripts writing wrong relative paths); malicious or malformed dependency packages.","solutions":["Fix the dependency's package.json so every exports target resolves to a file inside the package (targets must start with \"./\").","Move the referenced file into the package and update the exports target accordingly.","Reinstall the dependency in case of a corrupted or mislinked install.","If it's your own monorepo package, restructure so shared code is a separate package instead of an upward reference."],"exampleFix":"// before (packages/foo/package.json)\n\"exports\": { \"./x\": \"../shared/x.js\" }\n\n// after\n\"exports\": { \"./x\": \"./dist/x.js\" }","handlingStrategy":"validation","validationCode":"import { resolve, relative, dirname } from \"node:path\";\nfunction exportTargetStaysInPackage(pkgDir, target) {\n  if (typeof target !== \"string\" || !target.startsWith(\"./\")) return false;\n  const rel = relative(pkgDir, resolve(pkgDir, target));\n  return rel !== \"..\" && !rel.startsWith(\"../\") && !rel.startsWith(\"..\\\\\");\n}","typeGuard":"function isSafeRelativeTarget(target: string): boolean {\n  return target.startsWith(\"./\") && !target.includes(\"..\");\n}","tryCatchPattern":"try {\n  await graph.expand();\n} catch (e) {\n  if (e instanceof Error && e.message === \"package export escapes package root\") {\n    console.error(\"A dependency's exports target points outside its package; fix or replace that package.\");\n  }\n  throw e;\n}","preventionTips":["Audit dependency package.json files in CI for exports targets referencing \"..\".","Keep generated package.json exports paths relative to the package root.","Avoid hand-editing exports maps; generate them relative to dist/."],"tags":["module-resolution","path-traversal","security"],"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"}