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
- 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
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 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
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
- Existing clone at ${targetDir} has no remote.origin — refusi
- Invalid URL
- Only https:// and http:// git URLs are allowed
- Cloning from private/internal addresses is not allowed
- Clone target must be a subdirectory of ${CLONE_ROOT}
AI-assisted analysis of abhigyanpatwari/GitNexus@0d1aed942f (2026-08-20).
Data as JSON: /api/errors/e99e0827888d783c.
Report an issue: GitHub.