{"record":{"id":"0764b21678cdaaae","repo":"Mintplex-Labs/anything-llm","slug":"plugin-handler-does-not-pass-path-validation","errorCode":null,"errorMessage":"Plugin handler does not pass path validation.","messagePattern":"Plugin handler does not pass path validation\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/utils/agents/imported.js","lineNumber":21,"sourceCode":"const { safeJsonParse } = require(\"../http\");\nconst { isWithin, normalizePath } = require(\"../files\");\nconst { CollectorApi } = require(\"../collectorApi\");\nconst pluginsPath =\n  process.env.NODE_ENV === \"development\"\n    ? path.resolve(__dirname, \"../../storage/plugins/agent-skills\")\n    : path.resolve(process.env.STORAGE_DIR, \"plugins\", \"agent-skills\");\nconst sharedWebScraper = new CollectorApi();\n\nclass ImportedPlugin {\n  constructor(config) {\n    this.config = config;\n    this.handlerLocation = path.resolve(\n      pluginsPath,\n      normalizePath(this.config.hubId),\n      \"handler.js\"\n    );\n    if (!isWithin(pluginsPath, this.handlerLocation))\n      throw new Error(\"Plugin handler does not pass path validation.\");\n    delete require.cache[require.resolve(this.handlerLocation)];\n    this.handler = require(this.handlerLocation);\n    this.name = config.hubId;\n    this.startupConfig = {\n      params: {},\n    };\n  }\n\n  /**\n   * Gets the imported plugin handler.\n   * @param {string} hubId - The hub ID of the plugin.\n   * @returns {ImportedPlugin} - The plugin handler.\n   */\n  static loadPluginByHubId(hubId) {\n    const configLocation = path.resolve(\n      pluginsPath,\n      normalizePath(hubId),\n      \"plugin.json\"","sourceCodeStart":3,"sourceCodeEnd":39,"githubUrl":"https://github.com/Mintplex-Labs/anything-llm/blob/526360e320da9d1b36074be5ed64fe76e5bbfbbd/server/utils/agents/imported.js#L3-L39","documentation":"Thrown in the ImportedPlugin constructor (server/utils/agents/imported.js:20) when the resolved handler.js location is not within the plugins/agent-skills directory (isWithin returns false). It is a path-traversal guard (CWE-22) that runs after normalizePath on config.hubId, blocking hubIds containing '..' or absolute paths from escaping the plugin sandbox before the require() call.","triggerScenarios":"new ImportedPlugin(config) or ImportedPlugin.loadPluginByHubId(hubId) with a hubId containing traversal sequences ('..'), an absolute path, or a symlink that resolves outside pluginsPath.","commonSituations":"Corrupted/tampered plugin.json with a malicious hubId; manually renamed plugin folders; a community plugin whose hubId was crafted; STORAGE_DIR mis-set so even valid hubIds resolve outside the expected directory.","solutions":["Sanitize hubId to a single safe folder name (e.g. ^[a-z0-9-_]+$) before constructing the plugin.","Regenerate the offending plugin's plugin.json with a clean hubId, or delete and re-import it.","Confirm STORAGE_DIR is set correctly so pluginsPath resolves to the intended directory."],"exampleFix":"// before\nconst plugin = new ImportedPlugin({ hubId: req.body.hubId });\n// after\nif (!/^[a-z0-9-_]+$/i.test(req.body.hubId)) return res.status(400).send('invalid hubId');\nconst plugin = new ImportedPlugin({ hubId: req.body.hubId });","handlingStrategy":"validation","validationCode":"function safeHubId(hubId) {\n  return typeof hubId === 'string' && /^[a-z0-9-_]+$/i.test(hubId) && !hubId.includes('..');\n}\nif (!safeHubId(config.hubId)) throw new Error('Invalid hubId');","typeGuard":null,"tryCatchPattern":"try { const p = new ImportedPlugin(config); } catch (e) { if (/path validation/.test(e.message)) return rejectPlugin('unsafe hubId'); throw e; }","preventionTips":["Validate hubId against a strict allow-list regex before any file operation.","Never pass raw user input as a path component.","Keep isWithin as defense-in-depth after normalization."],"tags":["security","path-traversal","plugins","cwe-22","agents"],"backgroundTag":null,"analyzedSha":"526360e320da9d1b36074be5ed64fe76e5bbfbbd","analyzedAt":"2026-08-13T01:45:47.170Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}