{"record":{"id":"520285eb83c45f4c","repo":"abhigyanpatwari/GitNexus","slug":"could-not-extract-a-valid-repository-name-from-url","errorCode":null,"errorMessage":"Could not extract a valid repository name from URL","messagePattern":"Could not extract a valid repository name from URL","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gitnexus/src/server/git-clone.ts","lineNumber":63,"sourceCode":" *\n * Throws if the URL does not yield a filesystem-safe last segment. A name\n * like `..` or `foo/bar` would otherwise let `getCloneDir(name)` escape the\n * clone root via path traversal.\n */\nexport function extractRepoName(url: string): string {\n  let trimmed = url.trim();\n  while (trimmed.endsWith('/')) trimmed = trimmed.slice(0, -1);\n  const withoutGit = trimmed.toLowerCase().endsWith('.git') ? trimmed.slice(0, -4) : trimmed;\n  const name = withoutGit.split(/[/:]/).filter(Boolean).pop() ?? '';\n  if (\n    !name ||\n    name === '.' ||\n    name === '..' ||\n    name === 'unknown' ||\n    name.startsWith('-') ||\n    !REPO_NAME_PATTERN.test(name)\n  ) {\n    throw new Error('Could not extract a valid repository name from URL');\n  }\n  return name;\n}\n\n/**\n * Derive a clone directory name for the web `/api/analyze` boundary.\n *\n * The API historically accepted Azure DevOps and similar URLs whose repo\n * segment contains spaces or other directory-unsafe characters by sanitizing\n * the final segment. Keep that compatibility at the web boundary while leaving\n * `extractRepoName()` strict for internal/security-sensitive callers.\n */\nexport function extractWebRepoName(url: string): string {\n  let trimmed = url.trim();\n  while (trimmed.endsWith('/')) trimmed = trimmed.slice(0, -1);\n  const withoutGit = trimmed.toLowerCase().endsWith('.git') ? trimmed.slice(0, -4) : trimmed;\n  const rawName = withoutGit.split(/[/:]/).filter(Boolean).pop() ?? '';\n  const safeName = sanitizeRepoName(rawName);","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/ac9a4e9abd8fd3058c070b72c23402a4f887929a/gitnexus/src/server/git-clone.ts#L45-L81","documentation":"extractRepoName takes the last path segment of a git URL (via parseRepoNameFromUrl) and requires a filesystem-safe name matching ^[a-zA-Z0-9._-]+$, additionally rejecting '.', '..', and the 'unknown' sentinel. Anything else throws, because getCloneDir would otherwise let names like '..' or 'foo/bar' escape the clone root via path traversal.","triggerScenarios":"POST /api/analyze (or any cloneOrPull caller) with a URL whose final segment is empty ('https://github.com/user/') or contains characters outside the safe set — spaces ('my repo'), CJK/unicode, backslashes, or shell metacharacters ('repo;ls').","commonSituations":"Trailing-slash or bare-host URLs; repositories with human-display names containing spaces/unicode; pasted URLs with a trailing fragment or whitespace; URLs where the repo slug itself is non-ASCII.","solutions":["Send the canonical HTTPS URL ending in the repo slug, optionally with .git","Ensure the repo slug is pure ASCII alphanumerics plus . _ - (rename or alias non-ASCII slugs)","Trim whitespace and strip fragments/queries from user-pasted URLs before submitting"],"exampleFix":"// before\nconst url = 'https://github.com/user/my repo';\nextractRepoName(url); // throws\n\n// after\nconst url = 'https://github.com/user/my-repo';\nextractRepoName(url); // 'my-repo'","handlingStrategy":"validation","validationCode":"const SAFE_NAME = /^[a-zA-Z0-9._-]+$/;\nfunction lastSegment(url) { return new URL(url).pathname.replace(/\\/+$/, '').split('/').pop() || ''; }\nfunction urlYieldsSafeRepoName(url) {\n  try { const n = lastSegment(url).replace(/\\.git$/, ''); return SAFE_NAME.test(n) && n !== '.' && n !== '..' && n !== 'unknown'; }\n  catch { return false; }\n}","typeGuard":"function isSafeRepoName(name) {\n  return typeof name === 'string' && name.length > 0 && name !== '.' && name !== '..' && name !== 'unknown' && /^[a-zA-Z0-9._-]+$/.test(name);\n}","tryCatchPattern":"try { targetPath = getCloneDir(extractRepoName(url)); }\ncatch (e) {\n  if (e.message === 'Could not extract a valid repository name from URL') throw new BadRequest(`unsupported repo url: ${url}`, 400);\n  throw e;\n}","preventionTips":["Normalize URLs client-side: trim, strip fragments/queries, drop trailing slashes","Reject or transliterate non-ASCII repo slugs before submission","Keep repo names to [A-Za-z0-9._-] by policy"],"tags":["git-clone","url-parsing","validation","path-traversal-guard"],"backgroundTag":"invalid-repository-url","analyzedSha":"ac9a4e9abd8fd3058c070b72c23402a4f887929a","analyzedAt":"2026-09-08T00:40:44.970Z","contentChangedAt":"2026-09-08T00:40:44.970Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}