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
- 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.
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
- 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.
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
- Plugin entry for "${record.id}" must be inside the plugin di
- Invalid route path: "${path}". Path cannot contain '..', '?'
- Worker ${label} must stay within the deployed artifact root.
- ${label} escapes workspace
- Path is outside the browsable root
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/d774aec71e64d351.
Report an issue: GitHub.