{"record":{"id":"50feba258a81880f","repo":"paperclipai/paperclip","slug":"ui-parser-path-escapes-package-directory-skippin","errorCode":null,"errorMessage":"UI parser path escapes package directory — skipping","messagePattern":"UI parser path escapes package directory — skipping","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"server/src/adapters/plugin-loader.ts","lineNumber":121,"sourceCode":"        \"Adapter declares unsupported UI parser contract version — skipping UI parser\",\n      );\n      return undefined;\n    }\n  } else {\n    logger.info(\n      { packageName },\n      \"Adapter has ./ui-parser export but no paperclip.adapterUiParser version — loading anyway (future versions may require it)\",\n    );\n  }\n\n  const uiParserExp = pkg.exports[\"./ui-parser\"];\n  const uiParserFile = typeof uiParserExp === \"string\"\n    ? uiParserExp\n    : (uiParserExp.import ?? uiParserExp.default);\n  const uiParserPath = path.resolve(packageDir, uiParserFile);\n\n  if (!uiParserPath.startsWith(packageDir + path.sep) && uiParserPath !== packageDir) {\n    logger.warn(\n      { packageName, uiParserFile },\n      \"UI parser path escapes package directory — skipping\",\n    );\n    return undefined;\n  }\n\n  if (!fs.existsSync(uiParserPath)) {\n    return undefined;\n  }\n\n  try {\n    const source = fs.readFileSync(uiParserPath, \"utf-8\");\n    logger.info(\n      { packageName, uiParserFile, size: source.length },\n      `Loaded UI parser from adapter package${contractVersion ? \"\" : \" (no version declared)\"}`,\n    );\n    return source;\n  } catch (err) {","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/paperclipai/paperclip/blob/a7e689b3c35347b529cb9f54c9b9a8575a3dcab6/server/src/adapters/plugin-loader.ts#L103-L139","documentation":"Security guard in the plugin-loader: the ./ui-parser export path is resolved against the adapter package directory and must stay inside it (path prefix packageDir + path.sep). Absolute paths or ../ traversal that escape the package directory are rejected with this warning and the parser is skipped — preventing a malicious or misbuilt adapter from making the server read arbitrary files.","triggerScenarios":"An adapter's package.json exports[\"./ui-parser\"] resolves outside the package directory — e.g. \"../../shared/ui-parser.js\", an absolute path, or a bundler emitting a wrong relative path during packaging.","commonSituations":"Hand-authored package.json exports; monorepo packaging tools rewriting relative paths; malicious third-party adapter probing for file reads.","solutions":["Fix the adapter's package.json so ./ui-parser points to a file inside the package (e.g. \"./dist/ui-parser.js\").","Rebuild/repack the adapter so the export target ships inside the package and the file exists there.","If this comes from a third-party package you do not control, treat it as suspicious and audit before use."],"exampleFix":"// adapter package.json — before\n\"exports\": { \"./ui-parser\": \"../../shared/ui-parser.js\" }\n\n// after\n\"exports\": { \"./ui-parser\": \"./dist/ui-parser.js\" }","handlingStrategy":"validation","validationCode":"import path from 'node:path';\n\nfunction isContainedExport(packageDir: string, exportPath: string): boolean {\n  if (path.isAbsolute(exportPath)) return false;\n  const resolved = path.resolve(packageDir, exportPath);\n  return resolved.startsWith(packageDir + path.sep);\n}\n\n// audit before loading a third-party adapter:\nif (!isContainedExport(adapterDir, pkg.exports['./ui-parser'])) {\n  throw new Error(`Refusing adapter with escaping ui-parser export`);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Install adapters only from trusted registries and audit their package.json exports.","For in-house adapters, use bundler output paths under ./dist and add a lint rule enforcing it.","Never bypass the loader's containment guard to 'make it work'.","Treat this warn on a third-party package as a red flag, not an inconvenience."],"tags":["security","path-traversal","package-exports","adapter","plugin-loader"],"backgroundTag":"path-traversal-blocked","analyzedSha":"a7e689b3c35347b529cb9f54c9b9a8575a3dcab6","analyzedAt":"2026-08-18T22:49:45.177Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}