{"record":{"id":"156ad24cf331c4f4","repo":"mastra-ai/mastra","slug":"plugin-entry-for-record-id-must-be-inside-the","errorCode":null,"errorMessage":"Plugin entry for \"${record.id}\" must be inside the plugin directory","messagePattern":"Plugin entry for \"(.+?)\" must be inside the plugin directory","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"mastracode/sdk/src/plugins/loader.ts","lineNumber":134,"sourceCode":"\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}\n\nfunction resolveExistingAssetDirs(pluginRoot: string, dirname: 'skills' | 'commands'): string[] {\n  const dir = path.join(pluginRoot, dirname);\n  try {\n    return fs.statSync(dir).isDirectory() ? [dir] : [];\n  } catch {\n    return [];\n  }\n}","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/sdk/src/plugins/loader.ts#L116-L152","documentation":"resolvePluginEntryPath joins record.entry onto the plugin root and requires the result to stay inside that directory. This applies to ALL plugins (any source), preventing a registry entry from making the loader import a file outside the plugin's own directory — another path-traversal guard.","triggerScenarios":"record.entry containing '../' segments (e.g. '../shared/plugin.ts' or an absolute path elsewhere on disk) in a hand-edited plugins.json; entry pointing at a sibling plugin's file; typos like './..' entries after manual edits.","commonSituations":"Sharing one entry file between multiple plugin records via '../'; moving the entry file out of the plugin directory and patching record.path to compensate; malicious registry tampering.","solutions":["Set record.entry to a path relative to the plugin root that stays inside it, e.g. 'src/index.ts' or 'dist/plugin.js' (extension still must be .ts at load time).","Reinstall the plugin to regenerate a correct entry value.","Copy the shared code into the plugin directory instead of referencing it via '../'."],"exampleFix":"// before (plugins.json)\n{ \"id\": \"widgets\", \"entry\": \"../common/entry.ts\", ... }\n// after\n{ \"id\": \"widgets\", \"entry\": \"./entry.ts\", ... } // file lives inside the plugin dir","handlingStrategy":"validation","validationCode":"import path from 'node:path';\nfunction entryInsidePluginRoot(pluginRoot: string, entry: string): boolean {\n  const resolved = path.resolve(pluginRoot, entry);\n  return resolved === path.resolve(pluginRoot) || resolved.startsWith(path.resolve(pluginRoot) + path.sep);\n}\nif (!entryInsidePluginRoot(pluginRoot, record.entry)) throw new Error('entry must stay inside the plugin directory');","typeGuard":null,"tryCatchPattern":"const loaded = await loadPluginRecord(record, options);\nif (loaded.status === 'load failed' && loaded.error?.includes('Plugin entry') && loaded.error.includes('inside the plugin directory')) {\n  console.error(`Entry \"${record.entry}\" escapes the plugin root; use a relative path inside the plugin.`);\n}","preventionTips":["Always express record.entry relative to the plugin root with no '../' segments.","Copy shared code into each plugin rather than referencing sibling files.","Lint plugins.json entries to reject '..' before loading."],"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"}