{"record":{"id":"870d9764007b1e6e","repo":"EveryInc/compound-engineering-plugin","slug":"invalid-label-entry-paths-must-stay-within","errorCode":null,"errorMessage":"Invalid ${label}: ${entry}. Paths must stay within the plugin root.","messagePattern":"Invalid (.+?): (.+?)\\. Paths must stay within the plugin root\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/parsers/claude.ts","lineNumber":269,"sourceCode":"\nfunction unwrapMcpServers(raw: Record<string, unknown>): Record<string, ClaudeMcpServer> {\n  if (raw.mcpServers && typeof raw.mcpServers === \"object\") {\n    return raw.mcpServers as Record<string, ClaudeMcpServer>\n  }\n  return raw as Record<string, ClaudeMcpServer>\n}\n\nfunction mergeMcpConfigs(configs: Record<string, ClaudeMcpServer>[]): Record<string, ClaudeMcpServer> {\n  return configs.reduce((acc, config) => ({ ...acc, ...config }), {})\n}\n\nfunction resolveWithinRoot(root: string, entry: string, label: string): string {\n  const resolvedRoot = path.resolve(root)\n  const resolvedPath = path.resolve(root, entry)\n  if (resolvedPath === resolvedRoot || resolvedPath.startsWith(resolvedRoot + path.sep)) {\n    return resolvedPath\n  }\n  throw new Error(`Invalid ${label}: ${entry}. Paths must stay within the plugin root.`)\n}\n","sourceCodeStart":251,"sourceCodeEnd":271,"githubUrl":"https://github.com/EveryInc/compound-engineering-plugin/blob/c9c10f8c75412c7232cb2bd663e5fd1cea98d84e/src/parsers/claude.ts#L251-L271","documentation":"`resolveWithinRoot()` resolves a declared path entry against the plugin root and enforces that the result stays inside that root. If the entry escapes (via `..`, absolute paths, or symlinks resolving outside), the parser throws this error to block path traversal from plugin manifests.","triggerScenarios":"Calling the Claude parser on a plugin whose `.claude-plugin/plugin.json` (or component dir config) declares entries like `\"../shared/skills\"`, an absolute path such as `/etc/…`, or `\"../../outside\"` for skills/agents/commands directories.","commonSituations":"Authoring a plugin that tries to share files across skill/plugin boundaries with `..` traversal (explicitly banned by this repo's conventions); converting a third-party plugin with absolute paths from another machine; moving a plugin checkout without fixing absolute paths in its manifest.","solutions":["Change the manifest entry to a path relative to the plugin root (e.g. `skills/ce-plan` instead of `../ce-plan`)","Copy shared files into the plugin rather than referencing them via traversal","Remove any absolute paths from plugin.json and re-declare them as relative paths","If you own the plugin, restructure so all referenced assets live under the plugin root"],"exampleFix":"// before (plugin.json)\n{ \"skills\": { \"source\": \"../shared-skills\" } }\n// after\n{ \"skills\": { \"source\": \"skills\" } }","handlingStrategy":"validation","validationCode":"import path from \"node:path\"\nfunction staysWithinRoot(root: string, entry: string): boolean {\n  const r = path.resolve(root)\n  const p = path.resolve(root, entry)\n  return p === r || p.startsWith(r + path.sep)\n}\nif (!staysWithinRoot(pluginRoot, declaredPath)) throw new Error(`Refusing: ${declaredPath} escapes the plugin root`)","typeGuard":"function isWithinRoot(root: string, entry: string): boolean {\n  const resolved = path.resolve(root, entry)\n  return resolved === path.resolve(root) || resolved.startsWith(path.resolve(root) + path.sep)\n}","tryCatchPattern":"try {\n  const plugin = await loadClaudePlugin(pluginRoot)\n} catch (error) {\n  if (String(error).includes(\"Paths must stay within the plugin root\")) {\n    console.error(\"Fix the escaping path in the plugin manifest; copy shared files into the plugin instead\")\n  } else throw error\n}","preventionTips":["Never use `..` or absolute paths in plugin manifest path entries","Copy shared assets into each skill/plugin directory (self-contained units)","Review manifests with a path-containment check in CI"],"tags":["parser","path-traversal","validation"],"backgroundTag":"path-escapes-plugin-root","analyzedSha":"c9c10f8c75412c7232cb2bd663e5fd1cea98d84e","analyzedAt":"2026-08-31T15:18:07.959Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}