{"record":{"id":"7acc073af4fc80aa","repo":"remix-run/remix","slug":"resolved-path-escapes-the-allowed-root-relative","errorCode":null,"errorMessage":"Resolved path escapes the allowed root: ${relativePath}","messagePattern":"Resolved path escapes the allowed root: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/lib/contained-path.ts","lineNumber":12,"sourceCode":"import * as path from 'node:path'\n\nexport function resolveContainedPath(rootDir: string, relativePath: string): string {\n  let resolvedRootDir = path.resolve(rootDir)\n  let resolvedPath = path.resolve(resolvedRootDir, relativePath)\n  let pathFromRoot = path.relative(resolvedRootDir, resolvedPath)\n\n  if (pathFromRoot === '' || (!pathFromRoot.startsWith('..') && !path.isAbsolute(pathFromRoot))) {\n    return resolvedPath\n  }\n\n  throw new Error(`Resolved path escapes the allowed root: ${relativePath}`)\n}\n","sourceCodeStart":1,"sourceCodeEnd":14,"githubUrl":"https://github.com/remix-run/remix/blob/9696913134be3a4423513d2775f7b31d6917c049/packages/cli/src/lib/contained-path.ts#L1-L14","documentation":"Thrown by resolveContainedPath when the resolved absolute path falls outside the allowed root directory. The library uses it as a path-traversal guard so CLI operations (writing/reading generated files) cannot touch files outside the project root.","triggerScenarios":"Calling resolveContainedPath(rootDir, relativePath) where relativePath (after resolving symlinks/absolute segments) points outside rootDir, e.g. '../../etc/passwd', an absolute path like '/etc/hosts', or a symlink inside the root that resolves elsewhere.","commonSituations":"User-supplied or config-derived paths (e.g. from remix.json) containing '../' segments, absolute paths, or symlinks pointing outside the app; generating routes to a path like '../shared/routes'.","solutions":["Remove '..' segments and absolute prefixes from the relative path so it stays under the root","If the target truly lives outside the root, pass the outer directory as rootDir","Check for symlinks inside the root that resolve outside and replace or relocate them"],"exampleFix":"// before\nresolveContainedPath(appRoot, '../shared/routes.ts')\n// after\nresolveContainedPath(sharedRoot, 'routes.ts')","handlingStrategy":"validation","validationCode":"const path = require('node:path')\nfunction isInsideRoot(root, rel) {\n  let resolved = path.resolve(root, rel)\n  return resolved === root || resolved.startsWith(root + path.sep)\n}\nif (!isInsideRoot(appRoot, relativePath)) throw new Error('reject before calling')","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Normalize user paths with path.normalize before passing","Reject absolute paths and '..' segments at input boundaries","Treat symlinks as untrusted; use fs.realpath before containment checks"],"tags":["path-traversal","filesystem","security","cli"],"backgroundTag":"path-traversal-blocked","analyzedSha":"9696913134be3a4423513d2775f7b31d6917c049","analyzedAt":"2026-08-27T19:55:01.024Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}