paperclipai/paperclip · error · Error

Refusing to write export file outside output directory

Error message

Refusing to write export file outside output directory: ${relativePath}

What it means

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.

Solutions

  1. Fix the export package so all entries resolve inside the chosen output directory; do not hand-craft paths with '..' segments.
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at cli/src/commands/client/company.ts:1230 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of paperclipai/paperclip@120ae5428f (2026-08-18). Data as JSON: /api/errors/d99711e7e82d0f9d. Report an issue: GitHub.

Appendix: source

Thrown at cli/src/commands/client/company.ts:1230

  for (const [relativePath, content] of Object.entries(exported.files)) {
    const normalized = relativePath.replace(/\\/g, "/");
    const filePath = resolveExportOutputPath(root, normalized);
    await mkdir(path.dirname(filePath), { recursive: true });
    const writeValue = portableFileEntryToWriteValue(content);
    if (typeof writeValue === "string") {
      await writeFile(filePath, writeValue, "utf8");
    } else {
      await writeFile(filePath, writeValue);
    }
  }
}

export function resolveExportOutputPath(root: string, relativePath: string): string {
  const resolvedRoot = path.resolve(root);
  const filePath = path.resolve(resolvedRoot, relativePath);
  const rootPrefix = resolvedRoot.endsWith(path.sep) ? resolvedRoot : `${resolvedRoot}${path.sep}`;
  if (filePath !== resolvedRoot && !filePath.startsWith(rootPrefix)) {
    throw new Error(`Refusing to write export file outside output directory: ${relativePath}`);
  }
  return filePath;
}

export async function confirmOverwriteExportDirectory(
  outDir: string,
  opts: { force?: boolean } = {},
): Promise<void> {
  const root = path.resolve(outDir);
  const stats = await stat(root).catch(() => null);
  if (!stats) return;
  if (!stats.isDirectory()) {
    throw new Error(`Export output path ${root} exists and is not a directory.`);
  }

  const entries = await readdir(root);
  if (entries.length === 0) return;

View on GitHub (pinned to 120ae5428f)