paperclipai/paperclip · error

ACPX provider package issuer for ${packageName} resolves out

Error message

ACPX provider package issuer for ${packageName} resolves outside the selected provider root

What it means

createAcpxPackageJsonResolver returns a resolver closure that, for each requested package, canonicalizes the issuer package.json path via realpathSync and verifies it lives inside the selected provider root. This error is thrown when the issuer manifest (explicitly passed, or defaulting to the provider root manifest) canonicalizes to a path outside canonicalRoot, typically via symlinks. It is a fail-closed containment check ensuring dependency resolution never anchors outside the verified provider installation.

Source

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

    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(
        `ACPX provider package ${packageName} resolves outside the selected provider root`,
      );
    }
    return packageJsonPath;
  };
}

function resolvePackageJsonFromIssuer(
  packageName: string,
  issuerPackageJsonPath: string,

View on GitHub (pinned to 01ad858492)

Solutions

  1. Inspect the issuerPackageJsonPath with `realpathSync` and confirm it is inside the provider root passed to createAcpxPackageJsonResolver; if not, pass the correct issuer or correct provider root.
  2. If the issuer is a symlink into a store outside the root, either relocate the installation so symlinks stay inside the root, or pass the in-root manifest path explicitly instead of relying on the default.
  3. Recreate the provider root (clean install of the ACPX provider packages) so realpath of the manifest lands inside it.
  4. Verify the root argument is the canonical (realpath) root, not a path whose realpath differs (e.g. macOS /var vs /private/var).

Example fix

// before
const resolver = createAcpxPackageJsonResolver(providerRoot);
resolver(pkg, "/opt/other-store/pkg/package.json"); // outside providerRoot
// after
const issuer = resolve(providerRoot, "node_modules/pkg/package.json");
const resolver = createAcpxPackageJsonResolver(realpathSync(providerRoot));
resolver(pkg, issuer); // issuer realpath stays inside canonicalRoot
Defensive patterns

Strategy: validation

Validate before calling

import { realpathSync } from "node:fs";
import { relative, isAbsolute } from "node:path";
function isInside(root: string, candidate: string): boolean {
  const r = relative(realpathSync(root), realpathSync(candidate));
  return r !== "" && !r.startsWith("..") && !isAbsolute(r);
}
if (!isInside(providerRoot, issuerPath)) throw new Error("issuer outside provider root");

Type guard

function issuerInsideRoot(providerRoot: string, issuerPath?: string): boolean {
  if (issuerPath === undefined) return true;
  try { return isInside(providerRoot, issuerPath); } catch { return false; }
}

Try / catch

try {
  const manifestPath = resolver(packageName, issuerPath);
} catch (err) {
  if (err instanceof Error && err.message.includes("issuer") && err.message.includes("outside the selected provider root")) {
    // re-anchor issuer inside provider root or rebuild provider installation
  } else throw err;
}

Prevention

When it happens

Trigger: Calling the resolver (or defaultPackageJsonResolver) with an issuerPackageJsonPath that is a symlink into another location, points to a package outside providerPackageRoot, or refers to a manifest that was moved/relinked after createAcpxPackageJsonResolver computed canonicalRoot.

Common situations: Global/pnpm-style symlinked node_modules where package dirs are symlinked into a virtual store outside the provider root; a stale or hand-built provider root where the issuer manifest is a relative path resolved wrongly; passing a workspace package.json from outside the ACPX provider installation as the issuer.

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/a9f72a4f09d61c25. Report an issue: GitHub.