{"record":{"id":"6e4d064e7d3b62a9","repo":"abhigyanpatwari/GitNexus","slug":"clone-target-parent-must-resolve-inside-root","errorCode":null,"errorMessage":"Clone target parent must resolve inside ${root}","messagePattern":"Clone target parent must resolve inside (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"gitnexus/src/server/git-clone.ts","lineNumber":493,"sourceCode":"              [err, quarantineError],\n              `Clone failed and partial checkout could not be quarantined: ${safeTarget}`,\n            );\n          }\n        }\n      }\n      throw err;\n    }\n  }\n\n  return safeTarget;\n}\n\nasync function assertPreRealpathContainment(root: string, target: string): Promise<void> {\n  const realRoot = await fs.realpath(root);\n  const realParent = await fs.realpath(path.dirname(target));\n  const parentRel = path.relative(realRoot, realParent);\n  if (parentRel.startsWith('..') || path.isAbsolute(parentRel)) {\n    throw new Error(`Clone target parent must resolve inside ${root}`);\n  }\n}\n\nasync function assertPostRealpathContainment(root: string, target: string): Promise<void> {\n  const realRoot = await fs.realpath(root);\n  const realTarget = await fs.realpath(target);\n  const rel = path.relative(realRoot, realTarget);\n  if (rel === '' || rel.startsWith('..') || path.isAbsolute(rel)) {\n    throw new Error(`Clone target must resolve inside ${root}`);\n  }\n}\n\nasync function assertNoSymlinkPath(\n  root: string,\n  target: string,\n  verifyOwnership = false,\n): Promise<void> {\n  const resolvedRoot = path.resolve(root);","sourceCodeStart":475,"sourceCodeEnd":511,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/0d1aed942f0e8b5d3bac27519fff441aceea722d/gitnexus/src/server/git-clone.ts#L475-L511","documentation":"assertPreRealpathContainment resolves the clone root and the target's parent directory to their real filesystem paths (following symlinks) and requires the parent to resolve inside the real root. This blocks symlink-based escapes — e.g. a parent directory that is a symlink pointing outside the clone root — before any clone/pull happens.","triggerScenarios":"targetDir's parent is a symlink to a location outside the clone root (fs.realpath of parent escapes realRoot); cloneRoot itself is a symlink resolving elsewhere while target's parent resolves outside; an attacker-planted symlink in the clone path hierarchy.","commonSituations":"macOS /tmp being a symlink to /private/tmp without normalizing the root; user-configured clone roots containing symlinked subdirectories; containers where /var symlinks to another mount; malicious setups attempting path escape via symlinks.","solutions":["Ensure the target's parent directory physically resides inside the clone root (no symlinks in the parent chain).","Resolve the clone root once with fs.realpath and build targetDir from the resolved root and repo name.","Remove or replace the offending symlink with a real directory.","If the root itself is a symlink (e.g. /tmp -> /private/tmp), pass the realpath of the root as allowedCloneRoot."],"exampleFix":"// before\nawait cloneOrPull({ url, targetDir: '/tmp/clones/repo' }); // /tmp is a symlink\n// after\nconst root = await fs.realpath('/tmp/clones');\nawait cloneOrPull({ url, targetDir: path.join(root, 'repo'), allowedCloneRoot: root });","handlingStrategy":"validation","validationCode":"import { fs } from 'node:fs/promises';\nconst realRoot = await fs.realpath(allowedCloneRoot);\nconst realParent = await fs.realpath(path.dirname(targetDir));\nif (path.relative(realRoot, realParent).startsWith('..')) {\n  throw new Error(`parent of ${targetDir} escapes clone root ${realRoot}`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await cloneOrPull(opts);\n} catch (err) {\n  if ((err as Error).message.startsWith('Clone target parent must resolve inside')) {\n    throw new Error(`Symlinked parent in ${opts.targetDir}; use a path under the real clone root.`, { cause: err });\n  }\n  throw err;\n}","preventionTips":["fs.realpath the clone root once at startup and build all targets from it.","Avoid symlinked directories anywhere in the clone hierarchy.","On macOS, remember /tmp is a symlink to /private/tmp — realpath roots before use.","Treat unexpected symlinks in the clone tree as a security incident."],"tags":["git","security","symlink"],"backgroundTag":"path-traversal-blocked","analyzedSha":"0d1aed942f0e8b5d3bac27519fff441aceea722d","analyzedAt":"2026-09-08T00:40:44.970Z","contentChangedAt":"2026-09-08T00:40:44.970Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}