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

  1. Fix the dependency's package.json so every exports target resolves to a file inside the package (targets must start with "./").
  2. Move the referenced file into the package and update the exports target accordingly.
  3. Reinstall the dependency in case of a corrupted or mislinked install.
  4. 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

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


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)