can1357/oh-my-pi · error · Error
Plugin "${entry.name}" dapAdapters path escapes the plugin d
Error message
Plugin "${entry.name}" dapAdapters path escapes the plugin directory What it means
Analogous to the lspServers check: when a plugin's dapAdapters field is a string, it names a debug-adapter config file (JSON or YAML) resolved against the installed plugin directory. #writeEmbeddedDapConfig enforces pathIsWithin(cachePath, sourcePath) so a plugin cannot read config files from outside its own directory, then copies it to .dap.json/.dap.yaml/.dap.yml inside the plugin cache path.
Source
Thrown at packages/coding-agent/src/extensibility/plugins/marketplace/manager.ts:396
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`);
}
const extension = path.extname(sourcePath).toLowerCase();
const targetFilename = extension === ".yaml" || extension === ".yml" ? `.dap${extension}` : ".dap.json";
const targetPath = path.join(cachePath, targetFilename);
const content = await Bun.file(sourcePath).text();
await Bun.write(targetPath, content);
return;
}
const targetPath = path.join(cachePath, ".dap.json");
await Bun.write(targetPath, `${JSON.stringify({ adapters: dapAdapters }, null, 2)}\n`);
}
/**
* Resolve plugin version from multiple sources:
* 1. Catalog entry version (if set)
* 2. Plugin manifest (.claude-plugin/plugin.json, Agent Plugins root plugin.json, or package.json)
* 3. Git SHA from source (truncated to 7 chars)View on GitHub (pinned to 9690622007)
Solutions
- Point dapAdapters at a config file inside the plugin directory ("./dap.json" or "./dap.yaml")
- Inline the debug adapter definitions as an object in the dapAdapters field instead of a path
- Copy the YAML/JSON config into the plugin's own directory and republish
- Ask the maintainer to fix the manifest path
Example fix
// before (catalog entry) "dapAdapters": "../../debug/adapters.yaml" // after "dapAdapters": "./dap.yaml" // or an inline object of adapter definitions
Defensive patterns
Strategy: validation
Validate before calling
import * as path from "node:path";
function dapPathIsContained(cachePath: string, dapAdapters: string | object | undefined): boolean {
if (typeof dapAdapters !== "string") return true;
const resolved = path.resolve(cachePath, dapAdapters);
return resolved === cachePath || resolved.startsWith(cachePath + path.sep);
}
if (!dapPathIsContained(cachePath, entry.dapAdapters)) throw new Error("dapAdapters escapes plugin dir"); Type guard
function isContainedDapAdapters(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("dapAdapters path escapes")) {
logger.error("Refusing plugin: insecure dapAdapters path", { name, marketplace });
} else throw err;
} Prevention
- Keep dap adapter config files inside each plugin's directory
- Avoid absolute paths in dapAdapters — they almost never transfer between machines
- Review plugin manifests for traversal-looking paths before installing untrusted plugins
- Use inline object form of dapAdapters when possible to eliminate path resolution entirely
When it happens
Trigger: installPlugin on a plugin whose catalog entry sets dapAdapters to a string path that resolves outside cachePath — e.g. "../dap/adapters.yaml" or an absolute path elsewhere on disk.
Common situations: Plugin authors referencing a shared debug-adapter config in a sibling directory; absolute paths baked into manifests from another machine; symlinks escaping the plugin directory.
Related errors
- Plugin "${entry.name}" lspServers path escapes the plugin di
- 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/b4b42501da04f23e.
Report an issue: GitHub.