{"record":{"id":"fd436499a0a7c77f","repo":"can1357/oh-my-pi","slug":"invalid-path-localpath-resolves-outside-workin","errorCode":null,"errorMessage":"Invalid path: ${localPath} resolves outside working directory","messagePattern":"Invalid path: (.+?) resolves outside working directory","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/extensibility/plugins/installer.ts","lineNumber":150,"sourceCode":"\t\t\t\tmanifest: pkg.omp || pkg.pi || { version: pkg.version },\n\t\t\t\tenabledFeatures: null,\n\t\t\t\tenabled: true,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn plugins;\n}\n\nexport async function linkPlugin(localPath: string): Promise<void> {\n\tconst cwd = getProjectDir();\n\tconst absolutePath = path.resolve(cwd, localPath);\n\n\t// Validate that resolved path is within cwd to prevent path traversal\n\tconst normalizedCwd = path.resolve(cwd);\n\tconst normalizedPath = path.resolve(absolutePath);\n\tif (!normalizedPath.startsWith(`${normalizedCwd}/`) && normalizedPath !== normalizedCwd) {\n\t\tthrow new Error(`Invalid path: ${localPath} resolves outside working directory`);\n\t}\n\n\t// Validate package.json exists\n\tconst pkgFile = Bun.file(path.join(absolutePath, \"package.json\"));\n\tif (!(await pkgFile.exists())) {\n\t\tthrow new Error(`package.json not found at ${absolutePath}`);\n\t}\n\n\tlet pkg: { name?: string };\n\ttry {\n\t\tpkg = await pkgFile.json();\n\t} catch (err) {\n\t\tthrow new Error(`Invalid package.json at ${absolutePath}: ${err}`);\n\t}\n\n\tif (!pkg.name || typeof pkg.name !== \"string\") {\n\t\tthrow new Error(\"package.json must have a valid name field\");\n\t}","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/extensibility/plugins/installer.ts#L132-L168","documentation":"linkPlugin links a local directory into the plugins tree for development. To prevent path traversal it resolves the supplied localPath against cwd and requires the result to be equal to or inside the resolved cwd. This error is thrown when the resolved absolute path escapes the working directory.","triggerScenarios":"linkPlugin(localPath, cwd) is called with a path containing `..` segments, an absolute path in a different tree, or a symlink-resolving parent that normalizes outside cwd — e.g. linkPlugin(\"../../shared-plugin\") or linkPlugin(\"/opt/plugins/foo\", cwd).","commonSituations":"Keeping plugin source outside the project (a monorepo sibling directory) and passing a relative path that climbs out; absolute paths pasted from elsewhere; CI working directories that differ from local ones so a previously-valid relative path now escapes.","solutions":["Move or symlink the plugin directory inside the current working directory and link that path","Pass a path relative to cwd that does not traverse upward with `..`","If the plugin legitimately lives elsewhere, copy it into the project rather than linking","Use the project's supported mechanism for external plugins (install from npm/git) instead of linkPlugin"],"exampleFix":"// before (escapes cwd)\nawait linkPlugin(\"../../shared/my-plugin\");\n// after (path inside cwd)\nawait linkPlugin(\"plugins-local/my-plugin\");","handlingStrategy":"validation","validationCode":"const abs = path.resolve(cwd, localPath);\nconst inside = abs === path.resolve(cwd) || abs.startsWith(path.resolve(cwd) + path.sep);\nif (!inside) throw new Error(`refusing to link outside cwd: ${localPath}`);\nif (!(await Bun.file(path.join(abs, \"package.json\")).exists())) {\n\tthrow new Error(`not a package root (no package.json): ${abs}`);\n}","typeGuard":null,"tryCatchPattern":"try {\n\tawait linkPlugin(localPath);\n} catch (err) {\n\tif (err instanceof Error && err.message.includes(\"resolves outside working directory\")) {\n\t\t// copy the plugin into the project or use a supported install mechanism\n\t}\n\tthrow err;\n}","preventionTips":["Keep plugin source directories inside the project working directory","Never pass absolute paths or `..`-climbing relative paths to linkPlugin","Resolve and assert the path stays inside cwd in your own tooling before calling"],"tags":["path-traversal","security","validation","plugins"],"backgroundTag":"path-escapes-working-directory","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}