{"record":{"id":"0617793798a640b3","repo":"slidevjs/slidev","slug":"code-snippet-path-escapes-the-project-root-src","errorCode":null,"errorMessage":"Code snippet path escapes the project root: ${src}","messagePattern":"Code snippet path escapes the project root: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/slidev/node/syntax/snippet.ts","lineNumber":128,"sourceCode":"\nexport function resolveSnippetImport(lineText: string, userRoot: string, slide: SlideInfo, allowedRoots: string[] = [userRoot]) {\n  const match = lineText.trimStart().match(RE_SNIPPET_IMPORT)\n  if (!match)\n    return null\n\n  let [, filepath = '', regionName = '', lang = '', meta = ''] = match\n  const dir = path.dirname(slide.source.filepath)\n  const src = slash(\n    filepath.startsWith('@/')\n      ? path.resolve(userRoot, filepath.slice(2))\n      : path.resolve(dir, filepath),\n  )\n\n  lang = lang.trim() || path.extname(filepath).slice(1)\n  meta = meta.trim()\n\n  if (!isPathInsideRoots(src, allowedRoots)) {\n    throw new Error(`Code snippet path escapes the project root: ${src}`)\n  }\n\n  const isAFile = fs.existsSync(src) && fs.statSync(src).isFile()\n  if (!isAFile) {\n    throw new Error(`Code snippet path not found: ${src}`)\n  }\n\n  let content = fs.readFileSync(src, 'utf8')\n\n  if (regionName) {\n    const lines = content.split(RE_NEWLINE)\n    const region = findRegion(lines, regionName.slice(1))\n    if (region) {\n      content = dedent(\n        lines\n          .slice(region.start, region.end)\n          .filter(l => !(region.re.start.test(l) || region.re.end.test(l)))\n          .join('\\n'),","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/slidevjs/slidev/blob/0d798ace5828966d9f77f62094d5f7a971ad98f7/packages/slidev/node/syntax/snippet.ts#L110-L146","documentation":"Thrown by resolveSnippetImport when the resolved absolute path of a <<< snippet import is not inside any of allowedRoots (userRoot + workspace root + extra roots). isPathInsideRoots rejects any path containing '..' traversal that escapes the root set. This is a security guard preventing deck files from reading arbitrary files outside the project.","triggerScenarios":"Writing <<< ../secrets.env or <<< /etc/passwd in a slide, or any snippet path that, after path.resolve, lands outside every allowed root. Triggered at parse time when markdown-it renders the snippet block.","commonSituations":"Snippets referencing shared code in a parent directory of the project root; absolute paths to system files; symlinks that resolve outside the roots; misconfigured userRoot (e.g. entry resolved to the wrong directory).","solutions":["Move the target file inside the project root (or a configured root) and reference it relatively.","If you legitimately need a parent directory, add it to the Slidev roots config so isPathInsideRoots accepts it.","Use the @/ alias to point at files under userRoot instead of ../ paths.","Check that userRoot/workspace root are correct (entry file location drives userRoot)."],"exampleFix":"// before: escapes the project root\n<<< ../shared/snippet.ts\n\n// after: keep sources inside the project\n<<< @/shared/snippet.ts\n// (place the file under <userRoot>/shared/snippet.ts)","handlingStrategy":"validation","validationCode":"import { isPathInsideRoots } from '@slidev/cli/node/utils'\nimport path from 'pathe'\nimport { slash } from '@antfu/utils'\n\nfunction snippetInsideRoots(filepath: string, dir: string, userRoot: string, roots: string[]): boolean {\n  const src = slash(\n    filepath.startsWith('@/') ? path.resolve(userRoot, filepath.slice(2)) : path.resolve(dir, filepath),\n  )\n  return isPathInsideRoots(src, roots)\n}\n\n// before rendering, validate <<< lines:\nif (!snippetInsideRoots(filepath, slideDir, userRoot, allowedRoots)) {\n  throw new Error(`Snippet escapes roots: ${filepath}`)\n}","typeGuard":"function snippetPathSafe(filepath: string, dir: string, userRoot: string, roots: string[]): boolean {\n  const src = slash(\n    filepath.startsWith('@/') ? path.resolve(userRoot, filepath.slice(2)) : path.resolve(dir, filepath),\n  )\n  return isPathInsideRoots(src, roots)\n}","tryCatchPattern":"try {\n  return resolveSnippetImport(line, userRoot, slide, allowedRoots)\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Code snippet path escapes the project root')) {\n    // drop the import or relocate the file inside the project\n    return null\n  }\n  throw e\n}","preventionTips":["Keep snippet target files inside the project root or a configured Slidev root.","Use the @/ alias for sources under userRoot instead of ../ paths.","Add legitimate parent dirs to the roots config when shared code lives above the project.","Reject user-supplied <<< paths in CI builds to prevent traversal."],"tags":["syntax","snippet","security","path-traversal"],"backgroundTag":null,"analyzedSha":"0d798ace5828966d9f77f62094d5f7a971ad98f7","analyzedAt":"2026-08-12T17:10:56.221Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}