mastra-ai/mastra · error · Error

Plugin path for "${record.id}" must be inside the ${record.s

Error message

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

What it means

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.

Source

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

    return {
      ...record,
      status: 'load failed',
      error: error instanceof Error ? error.message : String(error),
      tools: {},
      toolNames: [],
    };
  }
}

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);
}

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Fix record.path in plugins.json so it resolves within the scope plugin directory (a relative path under it).
  2. Reinstall the plugin so the registry regenerates a valid path.
  3. If you need the plugin elsewhere, place a symlink whose target still resolves inside the plugin directory, or reinstall into the desired scope.

Example fix

// before (plugins.json)
{ "id": "widgets", "source": "github", "path": "../../shared/widgets", ... }
// after
{ "id": "widgets", "source": "github", "path": "github.com/acme/widgets", ... } // inside scope plugin dir
Defensive patterns

Strategy: validation

Validate before calling

import path from 'node:path';
function pluginPathInsideScope(recordPath: string, scopeRoot: string): boolean {
  const resolved = path.resolve(path.isAbsolute(recordPath) ? recordPath : path.join(scopeRoot, recordPath));
  return resolved === path.resolve(scopeRoot) || resolved.startsWith(path.resolve(scopeRoot) + path.sep);
}

Try / catch

const loaded = await loadPluginRecord(record, options);
if (loaded.status === 'load failed' && loaded.error?.includes('must be inside the') && loaded.error.includes('plugin directory')) {
  console.error(`Registry path for ${record.id} escapes the plugin directory; fix plugins.json or reinstall.`);
}

Prevention

When it happens

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

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

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/d774aec71e64d351. Report an issue: GitHub.