abhigyanpatwari/GitNexus · error · Error
Invalid repository name
Error message
Invalid repository name
What it means
getCloneDir re-validates its repoName argument at the boundary — callers may pass names from test fixtures, scripts, or other sources that never went through extractRepoName. Any name failing REPO_NAME_PATTERN ^[a-zA-Z0-9._-]+$, or '.'/'..'/empty, throws before path.join(CLONE_ROOT, repoName), so the result can never escape the clone root.
Source
Thrown at gitnexus/src/server/git-clone.ts:60
const name = parseRepoNameFromUrl(url);
if (
!name ||
name === '.' ||
name === '..' ||
name === 'unknown' ||
!REPO_NAME_PATTERN.test(name)
) {
throw new Error('Could not extract a valid repository name from URL');
}
return name;
}
/** Get the clone target directory for a repo name. */
export function getCloneDir(repoName: string): string {
// Re-validate at the boundary even though extractRepoName already checked —
// callers may pass a repoName from another source (test fixtures, scripts).
if (!repoName || repoName === '.' || repoName === '..' || !REPO_NAME_PATTERN.test(repoName)) {
throw new Error('Invalid repository name');
}
return path.join(CLONE_ROOT, repoName);
}
// Cloud metadata hostnames that must never be reachable via user-supplied URLs
const BLOCKED_HOSTNAMES = new Set([
'localhost',
'metadata.google.internal',
'metadata.azure.com',
'metadata.internal',
]);
/**
* Validate a git URL to prevent SSRF attacks.
* Only allows https:// and http:// schemes. Blocks private/internal addresses,
* IPv6 private ranges, cloud metadata hostnames, and numeric IP encodings.
*/
export function validateGitUrl(url: string): void {View on GitHub (pinned to aac7515d2a)
Solutions
- Derive names with extractRepoName(url) (or sanitizeRepoName) before calling getCloneDir
- Restrict inputs to [A-Za-z0-9._-] and strip/reject everything else
- Import the exported REPO_NAME_PATTERN for shared validation
Example fix
// before
const dir = getCloneDir(userTypedName); // 'my repo' -> throws
// after
import { REPO_NAME_PATTERN } from '../server/git-clone.js';
const safe = REPO_NAME_PATTERN.test(userTypedName) ? userTypedName : sanitizeRepoName(userTypedName);
const dir = getCloneDir(safe); Defensive patterns
Strategy: type-guard
Validate before calling
import { REPO_NAME_PATTERN } from './git-clone.js';
function assertSafeRepoName(name) {
if (!REPO_NAME_PATTERN.test(name) || name === '.' || name === '..') {
throw new Error(`Invalid repository name: ${JSON.stringify(name)}`);
}
} Type guard
import { REPO_NAME_PATTERN } from './git-clone.js';
function isSafeRepoName(name) {
return typeof name === 'string' && name.length > 0 && name !== '.' && name !== '..' && REPO_NAME_PATTERN.test(name);
}
// narrow before use: if (isSafeRepoName(n)) dir = getCloneDir(n); Try / catch
try { return getCloneDir(repoName); }
catch (e) {
if (e.message === 'Invalid repository name') return getCloneDir(sanitizeRepoName(repoName)); // one repair attempt
throw e;
} Prevention
- Never pass raw user strings to getCloneDir — derive via extractRepoName/sanitizeRepoName
- Reuse the exported REPO_NAME_PATTERN for shared validation instead of re-implementing it
- In tests, generate fixture names from the safe charset
When it happens
Trigger: Directly calling getCloneDir('my repo'), getCloneDir('foo/bar'), getCloneDir('../escape'), or getCloneDir('') from a script/test/fixture instead of deriving the name via extractRepoName.
Common situations: Test fixtures inventing repo names with spaces or slashes; scripts passing user-typed display names straight through; refactors that route names around the validated extractor.
Related errors
- Could not extract a valid repository name from URL
- Clone target must be a subdirectory of ${CLONE_ROOT}
- Could not read ${GITNEXUS_RC_FILENAME}: ${(err as Error).mes
- Unable to read eval-server authentication from ${filePath}
- Analyzer identity directory changed while it was read: ${can
AI-assisted analysis of abhigyanpatwari/GitNexus@aac7515d2a (2026-08-20).
Data as JSON: /api/errors/6dc8467c8706dc33.
Report an issue: GitHub.