{"record":{"id":"e99e0827888d783c","repo":"abhigyanpatwari/GitNexus","slug":"existing-clone-at-targetdir-has-remote-stripu","errorCode":null,"errorMessage":"Existing clone at ${targetDir} has remote ${stripUrlCredentials(remoteUrl)}, not the requested URL ${stripUrlCredentials(requestedUrl)}","messagePattern":"Existing clone at (.+?) has remote (.+?), not the requested URL (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"gitnexus/src/server/git-clone.ts","lineNumber":288,"sourceCode":" * request for `https://gitlab.example/attacker/repo.git` would otherwise\n * collide with an existing `~/.gitnexus/repos/repo` cloned from a different\n * origin and `git pull --ff-only` would silently succeed against the wrong\n * remote.\n *\n * Exported so the comparison logic is testable in isolation against any\n * tmpdir-based fixture, without needing to populate CLONE_ROOT.\n */\nexport async function assertRemoteMatchesRequestedUrl(\n  targetDir: string,\n  requestedUrl: string,\n  timeoutMs?: number,\n): Promise<void> {\n  const remoteUrl = await getRemoteOriginUrl(targetDir, timeoutMs);\n  if (remoteUrl === null) {\n    throw new Error(`Existing clone at ${targetDir} has no remote.origin — refusing to pull`);\n  }\n  if (normalizeGitUrlForCompare(remoteUrl) !== normalizeGitUrlForCompare(requestedUrl)) {\n    throw new Error(\n      // Both URLs are echoed to the API caller and the server log, and either\n      // can carry `https://user:token@` userinfo — strip it here too (#2914).\n      `Existing clone at ${targetDir} has remote ${stripUrlCredentials(remoteUrl)}, ` +\n        `not the requested URL ${stripUrlCredentials(requestedUrl)}`,\n    );\n  }\n}\n\n/**\n * Clone or pull a git repository.\n * If targetDir doesn't exist: git clone --depth 1\n * If targetDir exists with .git: git pull --ff-only (after verifying the\n * existing clone's remote.origin matches the requested URL).\n *\n * Security:\n *   - targetDir must resolve inside CLONE_ROOT (~/.gitnexus/repos/). The\n *     path.relative containment barrier below is the inline canonical idiom\n *     CodeQL's js/path-injection sanitizer recognizes.","sourceCodeStart":270,"sourceCodeEnd":306,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/0d1aed942f0e8b5d3bac27519fff441aceea722d/gitnexus/src/server/git-clone.ts#L270-L306","documentation":"cloneOrPull found an existing clone at targetDir but its remote.origin.url — after normalizeGitUrlForCompare — does not equal the requested URL. Clone dirs are keyed by URL basename, so https://gitlab.example/attacker/repo.git and https://github.com/you/repo.git collide on the same ~/.gitnexus/repos/repo; without this check `git pull --ff-only` would silently fetch the original remote and you would analyze the wrong code. The message strips credentials (stripUrlCredentials, #2914) because both URLs can carry https://user:token@ userinfo.","triggerScenarios":"cloneOrPull('https://gitlab.example/attacker/repo.git', dir) when dir was originally cloned from https://github.com/you/repo.git (same basename 'repo'); requesting the same repo under a different spelling that normalization does not erase (different host, different path, http vs https where the comparator keeps scheme); a re-hosted mirror with a different canonical URL.","commonSituations":"Renamed/moved repositories where the old clone dir keeps the old origin; forks with identical names on different forges; switching a config between SSH-origin clones made manually and https URLs via the API (scheme/host differences are not normalized away); security testing the basename-collision vector.","solutions":["Delete the colliding clone directory (the dir named in the message, under GITNEXUS_HOME/repos) so the new URL gets a fresh clone","Or request the URL that matches the existing remote (shown, credentials-stripped, in the error message)","If you manage the checkout yourself, update remote.origin with `git remote set-url origin <requested-url>` before calling","Confirm the request URL is really the repo you want — same basename from a different host is exactly the situation this guard exists to stop"],"exampleFix":"# before: ~/.gitnexus/repos/repo cloned from gitlab, now requesting github\ncloneOrPull('https://github.com/you/repo.git', getCloneDir('repo'))  // throws\n# after: drop the stale dir so it re-clones from the requested origin\nrm -rf ~/.gitnexus/repos/repo\ncloneOrPull('https://github.com/you/repo.git', getCloneDir('repo'))  // fresh clone","handlingStrategy":"validation","validationCode":"import { getRemoteOriginUrl, assertRemoteMatchesRequestedUrl } from './git-clone.js';\nconst remoteUrl = await getRemoteOriginUrl(targetDir);\nif (remoteUrl !== null && remoteUrl !== requestedUrl) {\n  // basename collision: the existing dir belongs to a different origin\n  await fs.rm(targetDir, { recursive: true, force: true });\n}\nawait assertRemoteMatchesRequestedUrl(targetDir, requestedUrl); // now passes","typeGuard":null,"tryCatchPattern":"try {\n  await cloneOrPull(url, dir);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('not the requested URL')) {\n    // message lists both URLs (credentials-stripped): compare, then drop the stale dir\n    await fs.rm(dir, { recursive: true, force: true });\n    await cloneOrPull(url, dir);\n  } else throw err;\n}","preventionTips":["When you change a repo's canonical URL (rename, migration, new forge), delete the old dir under GITNEXUS_HOME/repos/<basename>","Keep one spelling of each repo URL in config — scheme/host differences are not normalized away","Log the (already credential-stripped) message rather than re-echoing raw URLs yourself"],"tags":["git","git-clone","remote-mismatch","wrong-repo-guard","basename-collision"],"backgroundTag":"git-remote-url-mismatch","analyzedSha":"0d1aed942f0e8b5d3bac27519fff441aceea722d","analyzedAt":"2026-08-20T23:29:22.980Z","contentChangedAt":"2026-08-20T23:29:22.980Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}