abhigyanpatwari/GitNexus · error · Error

Existing clone at ${targetDir} has remote ${stripUrlCredenti

Error message

Existing clone at ${targetDir} has remote ${stripUrlCredentials(remoteUrl)}, not the requested URL ${stripUrlCredentials(requestedUrl)}

What it means

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.

Source

Thrown at gitnexus/src/server/git-clone.ts:288

 * request for `https://gitlab.example/attacker/repo.git` would otherwise
 * collide with an existing `~/.gitnexus/repos/repo` cloned from a different
 * origin and `git pull --ff-only` would silently succeed against the wrong
 * remote.
 *
 * Exported so the comparison logic is testable in isolation against any
 * tmpdir-based fixture, without needing to populate CLONE_ROOT.
 */
export async function assertRemoteMatchesRequestedUrl(
  targetDir: string,
  requestedUrl: string,
  timeoutMs?: number,
): Promise<void> {
  const remoteUrl = await getRemoteOriginUrl(targetDir, timeoutMs);
  if (remoteUrl === null) {
    throw new Error(`Existing clone at ${targetDir} has no remote.origin — refusing to pull`);
  }
  if (normalizeGitUrlForCompare(remoteUrl) !== normalizeGitUrlForCompare(requestedUrl)) {
    throw new Error(
      // Both URLs are echoed to the API caller and the server log, and either
      // can carry `https://user:token@` userinfo — strip it here too (#2914).
      `Existing clone at ${targetDir} has remote ${stripUrlCredentials(remoteUrl)}, ` +
        `not the requested URL ${stripUrlCredentials(requestedUrl)}`,
    );
  }
}

/**
 * Clone or pull a git repository.
 * If targetDir doesn't exist: git clone --depth 1
 * If targetDir exists with .git: git pull --ff-only (after verifying the
 * existing clone's remote.origin matches the requested URL).
 *
 * Security:
 *   - targetDir must resolve inside CLONE_ROOT (~/.gitnexus/repos/). The
 *     path.relative containment barrier below is the inline canonical idiom
 *     CodeQL's js/path-injection sanitizer recognizes.

View on GitHub (pinned to 0d1aed942f)

Solutions

  1. Delete the colliding clone directory (the dir named in the message, under GITNEXUS_HOME/repos) so the new URL gets a fresh clone
  2. Or request the URL that matches the existing remote (shown, credentials-stripped, in the error message)
  3. If you manage the checkout yourself, update remote.origin with `git remote set-url origin <requested-url>` before calling
  4. 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

Example fix

# before: ~/.gitnexus/repos/repo cloned from gitlab, now requesting github
cloneOrPull('https://github.com/you/repo.git', getCloneDir('repo'))  // throws
# after: drop the stale dir so it re-clones from the requested origin
rm -rf ~/.gitnexus/repos/repo
cloneOrPull('https://github.com/you/repo.git', getCloneDir('repo'))  // fresh clone
Defensive patterns

Strategy: validation

Validate before calling

import { getRemoteOriginUrl, assertRemoteMatchesRequestedUrl } from './git-clone.js';
const remoteUrl = await getRemoteOriginUrl(targetDir);
if (remoteUrl !== null && remoteUrl !== requestedUrl) {
  // basename collision: the existing dir belongs to a different origin
  await fs.rm(targetDir, { recursive: true, force: true });
}
await assertRemoteMatchesRequestedUrl(targetDir, requestedUrl); // now passes

Try / catch

try {
  await cloneOrPull(url, dir);
} catch (err) {
  if (err instanceof Error && err.message.includes('not the requested URL')) {
    // message lists both URLs (credentials-stripped): compare, then drop the stale dir
    await fs.rm(dir, { recursive: true, force: true });
    await cloneOrPull(url, dir);
  } else throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@0d1aed942f (2026-08-20). Data as JSON: /api/errors/e99e0827888d783c. Report an issue: GitHub.