{"record":{"id":"d774aec71e64d351","repo":"mastra-ai/mastra","slug":"plugin-path-for-record-id-must-be-inside-the","errorCode":null,"errorMessage":"Plugin path for \"${record.id}\" must be inside the ${record.scope} plugin directory","messagePattern":"Plugin path for \"(.+?)\" must be inside the (.+?) plugin directory","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"mastracode/sdk/src/plugins/loader.ts","lineNumber":125,"sourceCode":"    return {\n      ...record,\n      status: 'load failed',\n      error: error instanceof Error ? error.message : String(error),\n      tools: {},\n      toolNames: [],\n    };\n  }\n}\n\nexport async function loadPluginFromEntry(entryPath: string): Promise<MastraCodePlugin> {\n  return validatePluginExport(await importPluginModule(entryPath));\n}\n\nexport function resolvePluginRoot(record: ScopedInstalledPluginRecord, options: PluginPathOptions): string {\n  const scopeRoot = path.resolve(getPluginRoot(record.scope, options));\n  const pluginRoot = path.resolve(path.isAbsolute(record.path) ? record.path : path.join(scopeRoot, record.path));\n  if (record.source === 'github' && !isInsideDirectory(pluginRoot, scopeRoot)) {\n    throw new Error(`Plugin path for \"${record.id}\" must be inside the ${record.scope} plugin directory`);\n  }\n  return pluginRoot;\n}\n\nexport function resolvePluginEntryPath(record: ScopedInstalledPluginRecord, options: PluginPathOptions): string {\n  const pluginRoot = resolvePluginRoot(record, options);\n  const entryPath = path.resolve(pluginRoot, record.entry);\n  if (!isInsideDirectory(entryPath, pluginRoot)) {\n    throw new Error(`Plugin entry for \"${record.id}\" must be inside the plugin directory`);\n  }\n  return entryPath;\n}\n\nexport function isInsideDirectory(targetPath: string, root: string): boolean {\n  const resolvedTarget = path.resolve(targetPath);\n  const resolvedRoot = path.resolve(root);\n  return resolvedTarget === resolvedRoot || resolvedTarget.startsWith(resolvedRoot + path.sep);\n}","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/sdk/src/plugins/loader.ts#L107-L143","documentation":"For plugins installed from GitHub (record.source === 'github'), resolvePluginRoot requires the resolved record.path to live inside the scope's plugin directory (global or project). This is a security boundary: a tampered registry entry must not point the loader outside the sandbox to arbitrary filesystem locations.","triggerScenarios":"A hand-edited or malicious plugins.json with record.path set to '../../..' or an absolute path outside the plugin root; symlinks resolving outside the scope directory; a manually relocated plugin directory while record.path still referenced the old relative location incorrectly.","commonSituations":"Manually moving a cloned plugin repo elsewhere and pointing record.path at it; editing plugins.json to share a plugin across scopes via '../'; a corrupted or attacker-modified registry file.","solutions":["Fix record.path in plugins.json so it resolves within the scope plugin directory (a relative path under it).","Reinstall the plugin so the registry regenerates a valid path.","If you need the plugin elsewhere, place a symlink whose target still resolves inside the plugin directory, or reinstall into the desired scope."],"exampleFix":"// before (plugins.json)\n{ \"id\": \"widgets\", \"source\": \"github\", \"path\": \"../../shared/widgets\", ... }\n// after\n{ \"id\": \"widgets\", \"source\": \"github\", \"path\": \"github.com/acme/widgets\", ... } // inside scope plugin dir","handlingStrategy":"validation","validationCode":"import path from 'node:path';\nfunction pluginPathInsideScope(recordPath: string, scopeRoot: string): boolean {\n  const resolved = path.resolve(path.isAbsolute(recordPath) ? recordPath : path.join(scopeRoot, recordPath));\n  return resolved === path.resolve(scopeRoot) || resolved.startsWith(path.resolve(scopeRoot) + path.sep);\n}","typeGuard":null,"tryCatchPattern":"const loaded = await loadPluginRecord(record, options);\nif (loaded.status === 'load failed' && loaded.error?.includes('must be inside the') && loaded.error.includes('plugin directory')) {\n  console.error(`Registry path for ${record.id} escapes the plugin directory; fix plugins.json or reinstall.`);\n}","preventionTips":["Keep record.path relative to the scope plugin directory; never use '../../' escapes.","Don't hand-edit registry paths to point at relocated plugin directories — reinstall.","Treat plugins.json as generated output; validate any manual edits against the scope root."],"tags":["security","path-traversal","plugin-loading","registry"],"backgroundTag":"path-traversal-blocked","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}