{"record":{"id":"74071effbb954cea","repo":"ReactiveX/rxjs","slug":"message","errorCode":null,"errorMessage":"${message}","messagePattern":"\\$\\{message\\}","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/migrate/src/node.ts","lineNumber":187,"sourceCode":"  }\n  const plan = await planMigrationFiles(options);\n  return options.write ? applyMigrationPlan(plan, { overwrite: options.overwrite }) : plan.files;\n}\n\nfunction safeOutputPath(outputRoot: string, outputName: string): string {\n  if (!outputName || outputName === '.' || isAbsolute(outputName)) {\n    throw new Error(`Output name must be a non-empty relative path: ${outputName || '<empty>'}`);\n  }\n  const outputPath = resolve(outputRoot, outputName);\n  if (outputPath === outputRoot) throw new Error(`Output name must identify a file below outputRoot: ${outputName}`);\n  assertContained(outputRoot, outputPath, `Output path is outside outputRoot: ${outputName}`);\n  return outputPath;\n}\n\nfunction assertContained(root: string, candidate: string, message: string): void {\n  const localPath = relative(root, candidate);\n  if (localPath === '' || (localPath !== '..' && !localPath.startsWith(`..${sep}`) && !isAbsolute(localPath))) return;\n  throw new Error(message);\n}\n\nasync function canonicalDirectory(path: string, label: string): Promise<string> {\n  const canonicalPath = await realpath(path);\n  const pathStats = await stat(canonicalPath);\n  if (!pathStats.isDirectory()) throw new Error(`${label} is not a directory: ${path}`);\n  return canonicalPath;\n}\n\nasync function canonicalFutureDirectory(path: string, label: string): Promise<string> {\n  const resolved = await canonicalFuturePath(path);\n  try {\n    const pathStats = await stat(path);\n    if (!pathStats.isDirectory()) throw new Error(`${label} is not a directory: ${path}`);\n  } catch (error: unknown) {\n    if (!isMissingPathError(error)) throw error;\n  }\n  return resolved;","sourceCodeStart":169,"sourceCodeEnd":205,"githubUrl":"https://github.com/ReactiveX/rxjs/blob/54796b38a57e6309f9861e174737479bb3f63f61/packages/migrate/src/node.ts#L169-L205","documentation":"Thrown by assertContained when a candidate path resolves to the root itself or escapes outside the given root directory (the relative path from root to candidate is '' or starts with '..' or is absolute). The migrate CLI uses it as a path-traversal guard so outputs and skill installs stay inside the project root. This is a safety invariant, not a filesystem failure.","triggerScenarios":"Calling planMigrationFiles, preflightPlanOutputs, or safeOutputPath with an output path like '../outside-dir' or '/etc' that resolves outside the resolved source/output root; also symlinked targets whose canonical path escapes the root.","commonSituations":"Passing an absolute output directory, using '..' segments in --output, or a symlink inside the project pointing elsewhere so the canonical path leaves the root.","solutions":["Change the output/skill target path so it resolves strictly inside the configured root directory","Remove '..' segments and absolute paths from output configuration","If a symlink intentionally points outside the root, replace it with a real directory or move the target inside the root","Pass the intended project root explicitly instead of relying on cwd resolution"],"exampleFix":"// before\nawait safeOutputPath(projectRoot, '/var/tmp/out');\n// after\nawait safeOutputPath(projectRoot, 'var-tmp-out'); // stays under projectRoot","handlingStrategy":"validation","validationCode":"import { relative, isAbsolute } from 'node:path';\nconst contained = (root: string, c: string) => {\n  const r = relative(root, c);\n  return r !== '' && (r === '..' || r.startsWith(`..${sep}`) || isAbsolute(r));\n};\nif (contained(root, candidate)) throw new TypeError(`Output escapes root: ${candidate}`);","typeGuard":"const isContainedPath = (root: string, candidate: string): boolean => {\n  const r = relative(root, candidate);\n  return r === '' || (r !== '..' && !r.startsWith(`..${sep}`) && !isAbsolute(r));\n};","tryCatchPattern":"try { await safeOutputPath(root, out); } catch (e) { if (e instanceof Error && /outside|escape/i.test(e.message)) { /* reconfigure output path */ } throw e; }","preventionTips":["Always build output paths with path.resolve(root, relativePortion)","Reject absolute output paths at the config layer","Test symlinks in the output chain"],"tags":["path-traversal","filesystem","validation"],"backgroundTag":"path-traversal-guard","analyzedSha":"54796b38a57e6309f9861e174737479bb3f63f61","analyzedAt":"2026-08-28T10:21:27.410Z","schemaVersion":2},"datasetVersion":"2026-08-28T11:17:15.048Z"}