{"id":"ee6f9da4f6595794","repo":"vitest-dev/vitest","slug":"benchmark-artifact-path-relativepath-resolves","errorCode":null,"errorMessage":"Benchmark artifact path \"${relativePath}\" resolves outside the project root (${root}). Paths passed to `writeResult` and `bench.from()` must point inside the project.","messagePattern":"Benchmark artifact path \"(.+?)\" resolves outside the project root \\((.+?)\\)\\. Paths passed to `writeResult` and `bench\\.from\\(\\)` must point inside the project\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/vitest/src/node/benchmark.ts","lineNumber":21,"sourceCode":"import { existsSync } from 'node:fs'\nimport { mkdir, readFile, writeFile } from 'node:fs/promises'\nimport { dirname, isAbsolute, resolve } from 'pathe'\n\nexport class BenchmarkManager {\n  constructor(private project: TestProject) {}\n\n  // Resolve a user-supplied path against the project root. Reject paths that\n  // escape the project root: `bench.from()` accepts arbitrary input, and we\n  // never want a benchmark file to be able to read or clobber files outside\n  // the workspace.\n  public resolve(relativePath: string): string {\n    const root = this.project.config.root\n    const absolute = isAbsolute(relativePath)\n      ? resolve(relativePath)\n      : resolve(root, relativePath)\n    const rootWithSep = root.endsWith('/') ? root : `${root}/`\n    if (absolute !== root && !absolute.startsWith(rootWithSep)) {\n      throw new Error(\n        `Benchmark artifact path \"${relativePath}\" resolves outside the project root (${root}). `\n        + `Paths passed to \\`writeResult\\` and \\`bench.from()\\` must point inside the project.`,\n      )\n    }\n    return absolute\n  }\n\n  async readResult(relativePath: string): Promise<BaselineData | null> {\n    const path = this.resolve(relativePath)\n    if (!existsSync(path)) {\n      return null\n    }\n    return JSON.parse(await readFile(path, 'utf-8')) as BaselineData\n  }\n\n  async writeResult(relativePath: string, data: BaselineData): Promise<void> {\n    const absolute = this.resolve(relativePath)\n    await mkdir(dirname(absolute), { recursive: true })","sourceCodeStart":3,"sourceCodeEnd":39,"githubUrl":"https://github.com/vitest-dev/vitest/blob/d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09/packages/vitest/src/node/benchmark.ts#L3-L39","documentation":"A path-traversal guard in BenchmarkManager.resolve(). Both bench.from() (reads a baseline) and writeResult (writes a benchmark artifact) resolve the user-supplied path against the project root and reject any result that escapes it. This prevents a benchmark config or baseline path from reading or clobbering files outside the workspace.","triggerScenarios":"Configuring `bench.from` or the benchmark output path with a value containing `../` that climbs above root, or an absolute path pointing outside the project root. Triggered when BenchmarkManager.resolve() computes an absolute path that is neither equal to root nor prefixed by `root/`.","commonSituations":"Setting bench baseline/output to `../../benchmarks/baseline.json`; symlinking the output dir outside the project; misconfigured monorepo where root is a sub-package but the baseline lives in the workspace root above it; CI that passes an absolute temp path.","solutions":["Keep benchmark baseline/output paths inside the project root (e.g. `./benchmarks/baseline.json`).","If the file legitimately lives above the current package, move it inside the project or adjust `test.root` so the path is contained.","Avoid `../` segments in bench.from / writeResult paths; use paths relative to the project root.","If you need cross-package baselines in a monorepo, configure each project's root to be the workspace root or copy the baseline into each package."],"exampleFix":"// before\nbench: { writeResult: () => writeResult('../shared/baseline.json') }\n// after\nbench: { writeResult: () => writeResult('./baseline.json') }","handlingStrategy":"validation","validationCode":"import { isAbsolute, resolve, relative } from 'node:path'\nfunction assertInsideRoot(root: string, p: string) {\n  const abs = isAbsolute(p) ? p : resolve(root, p)\n  const rel = relative(root, abs)\n  if (rel.startsWith('..')) throw new Error(`path escapes root: ${p}`)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep benchmark baseline/output paths relative to the project root with no `..` segments.","Validate paths in your bench config builder before passing to writeResult/bench.from.","In monorepos, set test.root so legitimate cross-package paths stay contained."],"tags":[],"analyzedSha":"d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09","analyzedAt":"2026-08-03T20:23:56.861Z","schemaVersion":2}