mastra-ai/mastra · error · Error

Plugin entry for "${record.id}" must be inside the plugin di

Error message

Plugin entry for "${record.id}" must be inside the plugin directory

What it means

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.

Source

Thrown at mastracode/sdk/src/plugins/loader.ts:134

export async function loadPluginFromEntry(entryPath: string): Promise<MastraCodePlugin> {
  return validatePluginExport(await importPluginModule(entryPath));
}

export function resolvePluginRoot(record: ScopedInstalledPluginRecord, options: PluginPathOptions): string {
  const scopeRoot = path.resolve(getPluginRoot(record.scope, options));
  const pluginRoot = path.resolve(path.isAbsolute(record.path) ? record.path : path.join(scopeRoot, record.path));
  if (record.source === 'github' && !isInsideDirectory(pluginRoot, scopeRoot)) {
    throw new Error(`Plugin path for "${record.id}" must be inside the ${record.scope} plugin directory`);
  }
  return pluginRoot;
}

export function resolvePluginEntryPath(record: ScopedInstalledPluginRecord, options: PluginPathOptions): string {
  const pluginRoot = resolvePluginRoot(record, options);
  const entryPath = path.resolve(pluginRoot, record.entry);
  if (!isInsideDirectory(entryPath, pluginRoot)) {
    throw new Error(`Plugin entry for "${record.id}" must be inside the plugin directory`);
  }
  return entryPath;
}

export function isInsideDirectory(targetPath: string, root: string): boolean {
  const resolvedTarget = path.resolve(targetPath);
  const resolvedRoot = path.resolve(root);
  return resolvedTarget === resolvedRoot || resolvedTarget.startsWith(resolvedRoot + path.sep);
}

function resolveExistingAssetDirs(pluginRoot: string, dirname: 'skills' | 'commands'): string[] {
  const dir = path.join(pluginRoot, dirname);
  try {
    return fs.statSync(dir).isDirectory() ? [dir] : [];
  } catch {
    return [];
  }
}

View on GitHub (pinned to 75dd419e61)

Solutions

  1. 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).
  2. Reinstall the plugin to regenerate a correct entry value.
  3. Copy the shared code into the plugin directory instead of referencing it via '../'.

Example fix

// before (plugins.json)
{ "id": "widgets", "entry": "../common/entry.ts", ... }
// after
{ "id": "widgets", "entry": "./entry.ts", ... } // file lives inside the plugin dir
Defensive patterns

Strategy: validation

Validate before calling

import path from 'node:path';
function entryInsidePluginRoot(pluginRoot: string, entry: string): boolean {
  const resolved = path.resolve(pluginRoot, entry);
  return resolved === path.resolve(pluginRoot) || resolved.startsWith(path.resolve(pluginRoot) + path.sep);
}
if (!entryInsidePluginRoot(pluginRoot, record.entry)) throw new Error('entry must stay inside the plugin directory');

Try / catch

const loaded = await loadPluginRecord(record, options);
if (loaded.status === 'load failed' && loaded.error?.includes('Plugin entry') && loaded.error.includes('inside the plugin directory')) {
  console.error(`Entry "${record.entry}" escapes the plugin root; use a relative path inside the plugin.`);
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/156ad24cf331c4f4. Report an issue: GitHub.