paperclipai/paperclip · error

ACPX provider node_modules resolves outside the selected pro

Error message

ACPX provider node_modules resolves outside the selected provider root

What it means

The resolver canonicalizes <canonicalRoot>/node_modules and requires the result to remain inside the canonical provider root. If node_modules is a symlink to an external directory (e.g. a shared pnpm store), dependency resolution would read packages from outside the vetted provider installation, so the library rejects the layout to keep dependency provenance auditable.

Source

Thrown at packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts:323

    manifest.includes("\0") ||
    resolve(manifest) !== manifest
  ) {
    throw new Error(
      "ACPX provider package manifest must be an explicit normalized absolute path",
    );
  }
  const canonicalRoot = realpathSync(root);
  const canonicalManifest = realpathSync(manifest);
  if (!pathIsInside(canonicalRoot, canonicalManifest)) {
    throw new Error(
      "ACPX provider package manifest resolves outside the selected provider root",
    );
  }
  const canonicalNodeModules = realpathSync(
    resolve(canonicalRoot, "node_modules"),
  );
  if (!pathIsInside(canonicalRoot, canonicalNodeModules)) {
    throw new Error(
      "ACPX provider node_modules resolves outside the selected provider root",
    );
  }
  return (packageName, issuerPackageJsonPath) => {
    const canonicalIssuer =
      issuerPackageJsonPath === undefined
        ? canonicalManifest
        : realpathSync(issuerPackageJsonPath);
    if (!pathIsInside(canonicalRoot, canonicalIssuer)) {
      throw new Error(
        `ACPX provider package issuer for ${packageName} resolves outside the selected provider root`,
      );
    }
    const packageJsonPath = realpathSync(
      resolvePackageJsonFromIssuer(packageName, canonicalIssuer),
    );
    if (!pathIsInside(canonicalNodeModules, packageJsonPath)) {
      throw new Error(

View on GitHub (pinned to 01ad858492)

Solutions

  1. Install provider dependencies so node_modules is a real directory inside the provider root (npm/yarn install directly in the provider package, or copy the tree into root)
  2. Use pnpm's node-linker=hoisted in .npmrc for the provider install to avoid symlinked layouts
  3. Point the provider root at the directory whose realpath actually contains node_modules
  4. In containers, mount/copy node_modules inside the provider root rather than linking from outside

Example fix

// before
ln -s /store/acpx-provider/node_modules /opt/acpx/provider/node_modules
// after
# .npmrc in provider root
node-linker=hoisted
$ npm install --prefix /opt/acpx/provider   # real node_modules inside root
Defensive patterns

Strategy: validation

Validate before calling

import fs from 'node:fs';
import path from 'node:path';
function nodeModulesInsideRoot(root: string): boolean {
  const canonicalRoot = fs.realpathSync(root);
  const nm = fs.realpathSync(path.resolve(canonicalRoot, 'node_modules'));
  return nm.startsWith(canonicalRoot + path.sep);
}

Try / catch

try {
  const resolver = createAcpxPackageJsonResolver(root);
} catch (err) {
  if (err instanceof Error && /node_modules resolves outside the selected provider root/.test(err.message)) {
    throw new Error(`Provider ${root} uses a symlinked node_modules store; reinstall with node-linker=hoisted or copy node_modules into the provider root`);
  }
  throw err;
}

Prevention

When it happens

Trigger: Provider root contains node_modules as a symlink pointing outside root, so realpathSync(resolve(canonicalRoot, 'node_modules')) escapes canonicalRoot; ancestor dependency resolution would then walk an unvetted store path.

Common situations: Installing the provider with pnpm (which symlinks node_modules/.pnpm and may link node_modules itself); a monorepo hoisting node_modules to the repo root while root is a subpackage; docker/volume setups that relocate node_modules outside the provider directory.

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 paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/ca0489b84f0f685e. Report an issue: GitHub.