can1357/oh-my-pi · error · Error
Plugin "${entry.name}" lspServers path escapes the plugin di
Error message
Plugin "${entry.name}" lspServers path escapes the plugin directory What it means
When a plugin's lspServers field is a string, it is treated as a path to an LSP config file resolved relative to the installed plugin directory (cachePath). #writeEmbeddedLspConfig validates with pathIsWithin that the resolved path stays inside the plugin directory, blocking path traversal out of the plugin sandbox. A malicious or buggy plugin manifest referencing ../../secrets/lsp.json is rejected.
Source
Thrown at packages/coding-agent/src/extensibility/plugins/marketplace/manager.ts:379
}
}
await this.#registerRuntimePlugin(scope, packageName, cachePath, version, wasDisabled ? false : undefined);
this.#clearCache();
logger.debug("Plugin installed", { pluginId, version, cachePath });
return installedEntry;
}
async #writeEmbeddedLspConfig(entry: MarketplacePluginEntry, cachePath: string): Promise<void> {
const lspServers = entry.lspServers;
if (!lspServers) return;
const targetPath = path.join(cachePath, ".lsp.json");
if (typeof lspServers === "string") {
const sourcePath = path.resolve(cachePath, lspServers);
if (!pathIsWithin(cachePath, sourcePath)) {
throw new Error(`Plugin "${entry.name}" lspServers path escapes the plugin directory`);
}
const content = await Bun.file(sourcePath).text();
await Bun.write(targetPath, content);
return;
}
await Bun.write(targetPath, `${JSON.stringify({ servers: lspServers }, null, 2)}\n`);
}
async #writeEmbeddedDapConfig(entry: MarketplacePluginEntry, cachePath: string): Promise<void> {
const dapAdapters = entry.dapAdapters;
if (!dapAdapters) return;
if (typeof dapAdapters === "string") {
const sourcePath = path.resolve(cachePath, dapAdapters);
if (!pathIsWithin(cachePath, sourcePath)) {
throw new Error(`Plugin "${entry.name}" dapAdapters path escapes the plugin directory`);
}View on GitHub (pinned to 9690622007)
Solutions
- Fix the plugin's lspServers field to point at a file inside the plugin directory (relative path within it)
- Inline the LSP server config directly in the plugin entry instead of referencing an external file
- If sharing config across plugins, duplicate the file into each plugin directory or restructure the marketplace so each plugin owns its config
- Contact the plugin maintainer to republish with a contained path
Example fix
// before (catalog entry) "lspServers": "../shared/lsp.json" // after "lspServers": "./lsp.json" // file placed inside this plugin's directory
Defensive patterns
Strategy: validation
Validate before calling
import * as path from "node:path";
function lspPathIsContained(cachePath: string, lspServers: string | object | undefined): boolean {
if (typeof lspServers !== "string") return true;
const resolved = path.resolve(cachePath, lspServers);
return resolved === cachePath || resolved.startsWith(cachePath + path.sep);
}
if (!lspPathIsContained(cachePath, entry.lspServers)) throw new Error("lspServers escapes plugin dir"); Type guard
function isContainedLspServers(v: unknown, cachePath: string): v is string {
return typeof v === "string" && (path.resolve(cachePath, v) === cachePath || path.resolve(cachePath, v).startsWith(cachePath + path.sep));
} Try / catch
try {
await manager.installPlugin(name, marketplace);
} catch (err) {
if (err instanceof Error && err.message.includes("lspServers path escapes")) {
logger.error("Refusing plugin: insecure lspServers path", { name, marketplace });
} else throw err;
} Prevention
- Only install plugins from marketplaces you trust; this error usually flags a malicious or sloppy manifest
- As a plugin author, keep all referenced config files inside the plugin directory
- Never use ../ or absolute paths in lspServers fields
- Audit third-party plugin manifests for path fields before installing
When it happens
Trigger: Installing (installPlugin) a marketplace plugin whose catalog entry sets lspServers to a string path that, after path.resolve against cachePath, escapes the plugin directory (e.g. "../shared/lsp.json", "/etc/lsp.json", or a symlinked path outside).
Common situations: Plugin authors using ../ to share one LSP config across sibling plugins in a monorepo-style marketplace; absolute paths in manifests that were valid on the author's machine; symlinks inside the plugin dir pointing outward.
Related errors
- Plugin "${entry.name}" dapAdapters path escapes the plugin d
- Plugin source "${source}" resolves outside marketplace root
- git-subdir path "${source.path}" escapes the cloned reposito
- Destination paths cannot contain parent traversal or NUL byt
- Shared-folder destination escapes its configured root
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/57b51b395d2e700f.
Report an issue: GitHub.