{"record":{"id":"98ca2e276f3827f9","repo":"abhigyanpatwari/GitNexus","slug":"clone-target-basename-must-match-repository-name","errorCode":null,"errorMessage":"Clone target basename must match repository name ${expectedRepoName}","messagePattern":"Clone target basename must match repository name (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gitnexus/src/server/git-clone.ts","lineNumber":342,"sourceCode":"  onProgress?: (progress: CloneProgress) => void,\n  options?: CloneOrPullOptions,\n): Promise<string> {\n  // Containment barrier — inline with the canonical path.relative idiom so\n  // CodeQL recognizes the sanitizer at every following filesystem and\n  // subprocess sink. The same `safeTarget` is used for every downstream\n  // path operation — no reassignment that the analyzer could lose track of.\n  //\n  // The lexical check runs before filesystem creation; realpath and symlink\n  // checks below run before pull/clone and again after clone completes.\n  const cloneRoot = path.resolve(options?.allowedCloneRoot ?? CLONE_ROOT);\n  const expectedRepoName = options?.expectedRepoName;\n  if (expectedRepoName !== undefined && expectedRepoName !== extractRepoName(url)) {\n    throw new Error(`Clone target repo name ${expectedRepoName} does not match requested URL`);\n  }\n\n  const safeTarget = path.resolve(targetDir);\n  if (expectedRepoName !== undefined && path.basename(safeTarget) !== expectedRepoName) {\n    throw new Error(`Clone target basename must match repository name ${expectedRepoName}`);\n  }\n\n  const rel = path.relative(cloneRoot, safeTarget);\n  if (rel === '' || rel.startsWith('..') || path.isAbsolute(rel)) {\n    throw new Error(`Clone target must be a subdirectory of ${cloneRoot}`);\n  }\n\n  // Always validate the requested URL — the prior shape only ran this in\n  // the code path where the repo was cloned. Now it runs unconditionally,\n  // preventing SSRF / blocked-host bypasses even when targetDir already exists.\n  if (options?.allowAutoSyncSsh) validateAutoSyncRemoteUrl(url);\n  else validateGitUrl(url);\n  await fs.mkdir(cloneRoot, { recursive: true });\n  if (options?.allowedCloneRoot) {\n    await assertDirectoryOwnerAndPermissions(cloneRoot);\n  }\n  await assertNoSymlinkPath(cloneRoot, safeTarget, Boolean(options?.allowedCloneRoot));\n  await fs.mkdir(path.dirname(safeTarget), { recursive: true });","sourceCodeStart":324,"sourceCodeEnd":360,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/0d1aed942f0e8b5d3bac27519fff441aceea722d/gitnexus/src/server/git-clone.ts#L324-L360","documentation":"cloneOrPull throws this when the basename of the resolved targetDir does not equal the supplied expectedRepoName. Together with the URL-name check this guarantees the clone lands in a directory named exactly after the repository, so downstream consumers can rely on targetDir === cloneRoot/<repoName>.","triggerScenarios":"Passing a targetDir like /clones/my-fork when expectedRepoName is 'my-repo'; appending a suffix such as /clones/my-repo-v2 to targetDir; reusing a cached targetDir built for a differently named repo.","commonSituations":"Versioned or suffixed local directories after a host-side repo rename; callers computing targetDir from user input while expectedRepoName comes from trusted config; migrating clone layouts to a new naming scheme.","solutions":["Set targetDir to a directory whose basename equals expectedRepoName (e.g. path.join(cloneRoot, expectedRepoName)).","Remove any '-v2', date, or fork suffixes from the target directory name.","Update expectedRepoName if the on-disk layout name is the intended source of truth.","Stop passing expectedRepoName if the basename constraint should not apply."],"exampleFix":"// before\nawait cloneOrPull({ url, expectedRepoName: 'gitnexus', targetDir: '/clones/gitnexus-mirror' });\n// after\nawait cloneOrPull({ url, expectedRepoName: 'gitnexus', targetDir: '/clones/gitnexus' });","handlingStrategy":"validation","validationCode":"const safeTarget = path.resolve(targetDir);\nif (expectedRepoName !== undefined && path.basename(safeTarget) !== expectedRepoName) {\n  throw new Error(`targetDir basename must be ${expectedRepoName}`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await cloneOrPull(opts);\n} catch (err) {\n  if ((err as Error).message.startsWith('Clone target basename')) {\n    opts.targetDir = path.join(path.dirname(opts.targetDir), opts.expectedRepoName);\n    return cloneOrPull(opts);\n  }\n  throw err;\n}","preventionTips":["Always build targetDir as path.join(cloneRoot, expectedRepoName).","Avoid suffixes/version tags in clone directory names; use separate clone roots per layout instead.","Enforce the <root>/<repoName> layout convention in code review."],"tags":["git","clone","path"],"backgroundTag":"invalid-argument-value","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"}