JuliusBrussee/caveman · error
package export escapes package root
Error message
package export escapes package root
What it means
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.
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.
Example fix
// before (packages/foo/package.json)
"exports": { "./x": "../shared/x.js" }
// after
"exports": { "./x": "./dist/x.js" } Defensive patterns
Strategy: validation
Validate before calling
import { resolve, relative, dirname } from "node:path";
function exportTargetStaysInPackage(pkgDir, target) {
if (typeof target !== "string" || !target.startsWith("./")) return false;
const rel = relative(pkgDir, resolve(pkgDir, target));
return rel !== ".." && !rel.startsWith("../") && !rel.startsWith("..\\");
} Type guard
function isSafeRelativeTarget(target: string): boolean {
return target.startsWith("./") && !target.includes("..");
} Try / catch
try {
await graph.expand();
} catch (e) {
if (e instanceof Error && e.message === "package export escapes package root") {
console.error("A dependency's exports target points outside its package; fix or replace that package.");
}
throw e;
} Prevention
- 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/.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- cave_live_eval_sandbox_profile_escapes_root
- cave_sandbox_source_read_grant_escapes_staging
- cave_tool_sandbox_entry_escapes_root
- cave_tool_sandbox_source_escapes_root
- caveman agent: dev escapes project root
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/06366d76b388eb4f.
Report an issue: GitHub.
Appendix: source
Thrown at packages/agent/src/source-graph.ts:141
return [base];
}
async function resolveImportOnlyDependency(specifier: string, importer: string): Promise<string> {
const packageName = barePackageName(specifier);
const packageJSONPath = resolvePackageJSON(packageName, importer);
const packageJSON = JSON.parse(await readFile(packageJSONPath, "utf8")) as {
exports?: unknown;
};
const subpath = specifier === packageName ? "." : `.${specifier.slice(packageName.length)}`;
const target = resolvePackageExport(packageJSON.exports, subpath);
if (target === undefined || !target.startsWith("./")) {
throw new Error("import export not found");
}
const packageRoot = dirname(packageJSONPath);
const absolute = resolve(packageRoot, target);
const relativeTarget = relative(packageRoot, absolute);
if (relativeTarget === ".." || relativeTarget.startsWith("../") || relativeTarget.startsWith("..\\")) {
throw new Error("package export escapes package root");
}
return absolute;
}
function dependencyPackageRoot(specifier: string, importer: string): string {
return dirname(resolvePackageJSON(barePackageName(specifier), importer));
}
function resolvePackageJSON(packageName: string, importer: string): string {
const packageJSONPath = findPackageJSON(packageName, pathToFileURL(importer));
if (packageJSONPath === undefined) throw new Error("package not found");
return packageJSONPath;
}
async function collectPackageClosure(
packageRoot: string,
files: Set<string>,
visitedRoots: Set<string>,View on GitHub (pinned to 3ee70a1026)