{"record":{"id":"0c11e9ef62320e03","repo":"paperclipai/paperclip","slug":"acpx-provider-package-packagename-resolves-outs","errorCode":null,"errorMessage":"ACPX provider package ${packageName} resolves outside the selected provider root","messagePattern":"ACPX provider package (.+?) resolves outside the selected provider root","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts","lineNumber":341,"sourceCode":"    throw new Error(\n      \"ACPX provider node_modules resolves outside the selected provider root\",\n    );\n  }\n  return (packageName, issuerPackageJsonPath) => {\n    const canonicalIssuer =\n      issuerPackageJsonPath === undefined\n        ? canonicalManifest\n        : realpathSync(issuerPackageJsonPath);\n    if (!pathIsInside(canonicalRoot, canonicalIssuer)) {\n      throw new Error(\n        `ACPX provider package issuer for ${packageName} resolves outside the selected provider root`,\n      );\n    }\n    const packageJsonPath = realpathSync(\n      resolvePackageJsonFromIssuer(packageName, canonicalIssuer),\n    );\n    if (!pathIsInside(canonicalNodeModules, packageJsonPath)) {\n      throw new Error(\n        `ACPX provider package ${packageName} resolves outside the selected provider root`,\n      );\n    }\n    return packageJsonPath;\n  };\n}\n\nfunction resolvePackageJsonFromIssuer(\n  packageName: string,\n  issuerPackageJsonPath: string,\n): string {\n  const issuerRequire = createRequire(issuerPackageJsonPath);\n  try {\n    return issuerRequire.resolve(`${packageName}/package.json`);\n  } catch (error) {\n    if (\n      (error as NodeJS.ErrnoException).code !== \"ERR_PACKAGE_PATH_NOT_EXPORTED\"\n    )","sourceCodeStart":323,"sourceCodeEnd":359,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts#L323-L359","documentation":"After resolving the requested package's package.json from the issuer, the resolver canonicalizes it with realpathSync and requires it to be inside the provider root's node_modules directory. This error is thrown when the package's manifest resolves elsewhere, meaning the dependency would be loaded from outside the verified provider installation. It is a fail-closed check against dependency confusion or hijacked resolution.","triggerScenarios":"Calling the resolver for a package whose `require.resolve('pkg/package.json')` (via the issuer's require) resolves outside `<providerRoot>/node_modules` — e.g. hoisted deps above the root, a peer dependency satisfied from the host tree, or a symlink pointing out of the store.","commonSituations":"Package installed globally or in a parent workspace instead of the provider root's node_modules; pnpm virtual-store symlink whose realpath lands outside canonicalRoot (canonicalNodeModules itself is a symlink); version change where the package became a peer/optional dep resolved from an ancestor directory.","solutions":["Install the missing/misplaced package into the provider root's own node_modules (`cd <providerRoot> && npm/bun install`) so it resolves in-place.","Compare `realpathSync(resolvedPath)` with `realpathSync(resolve(providerRoot,'node_modules'))`; if node_modules is a symlink, build the root so its realpath contains the dependency.","Check for accidental global installs (`npm ls -g <pkg>`) and remove/alias them so local resolution wins.","If the package is legitimately external, do not pass it through this resolver — it is only for packages belonging to the verified provider installation."],"exampleFix":"// before\n// dep hoisted outside provider root: /workspace/node_modules/@acpx/provider\nconst p = resolver(\"@acpx/provider\", issuerManifest); // resolves outside\n// after\ncd <providerRoot> && bun install @acpx/provider  # installs into providerRoot/node_modules\nconst p = resolver(\"@acpx/provider\", issuerManifest); // inside canonicalNodeModules","handlingStrategy":"validation","validationCode":"const nmRoot = realpathSync(resolve(providerRoot, \"node_modules\"));\nconst resolved = createRequire(issuerManifest).resolve(`${pkg}/package.json`);\nif (!isInside(nmRoot, realpathSync(resolved))) {\n  throw new Error(`${pkg} is not installed in the provider root's node_modules`);\n}","typeGuard":"function resolvesInsideNodeModules(providerRoot: string, issuer: string, pkg: string): boolean {\n  try {\n    const nm = realpathSync(resolve(providerRoot, \"node_modules\"));\n    const p = realpathSync(createRequire(issuer).resolve(`${pkg}/package.json`));\n    return isInside(nm, p);\n  } catch { return false; }\n}","tryCatchPattern":"try {\n  const manifestPath = resolver(pkg, issuer);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(pkg) && err.message.includes(\"outside the selected provider root\")) {\n    // install the package into providerRoot/node_modules or fix symlink layout\n  } else throw err;\n}","preventionTips":["Install all provider dependencies inside the provider root's own node_modules; never rely on hoisted ancestors or globals.","After installs, spot-check with `node -e \"console.log(require.resolve('<pkg>/package.json'))\"` rooted at the provider.","Keep dependency declarations pinned so resolution cannot silently move to a different tree.","Watch for tooling (workspaces, bundlers) that redirects resolution outside the provider installation."],"tags":["security","path-containment","dependency-resolution","symlink"],"backgroundTag":"path-traversal-blocked","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}