{"record":{"id":"6b7bdb5e68a186a9","repo":"stablyai/orca","slug":"invalid-repository-name-derived-from-url","errorCode":null,"errorMessage":"Invalid repository name derived from URL","messagePattern":"Invalid repository name derived from URL","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/main/git/repo-clone-path.ts","lineNumber":24,"sourceCode":"  normalizeRuntimePathForComparison,\n  normalizeRuntimePathSeparators\n} from '../../shared/cross-platform-path'\n\nexport type ClaimedCloneTarget = {\n  canCleanup: boolean\n  ownedDirectoryIdentity: CloneDirectoryIdentity | null\n}\n\ntype CloneDirectoryIdentity = Pick<Stats, 'dev' | 'ino' | 'birthtimeMs'>\n\nexport function deriveCloneRepoNameFromUrl(url: string): string {\n  // Why: direct callers can supply URLs whose default git clone folder would\n  // be \".\" or \"..\"; rejecting them prevents parent/destination deletion.\n  const source = url.replace(/\\.git\\/?$/, '')\n  const isWindowsLocalSource = /^[A-Za-z]:[\\\\/]/.test(source) || source.startsWith('\\\\\\\\')\n  const repoName = isWindowsLocalSource ? win32.basename(source) : posix.basename(source)\n  if (!repoName || repoName === '.' || repoName === '..') {\n    throw new Error('Invalid repository name derived from URL')\n  }\n  if (repoName.includes('/') || repoName.includes('\\\\')) {\n    throw new Error('Invalid repository name derived from URL')\n  }\n  return repoName\n}\n\nexport function deriveValidatedClonePath(args: { url: string; destination: string }): string {\n  if (\n    !args.destination ||\n    !isAbsolute(args.destination) ||\n    (process.platform !== 'win32' && isWindowsAbsolutePathLike(args.destination))\n  ) {\n    throw new Error('Clone destination must be an absolute path')\n  }\n\n  const repoName = deriveCloneRepoNameFromUrl(args.url)\n","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/src/main/git/repo-clone-path.ts#L6-L42","documentation":"deriveCloneRepoNameFromUrl strips a trailing .git (with optional slash), then takes the POSIX (or win32, for Windows-local sources) basename of the URL. If that basename is empty, '.', or '..', the function throws — these come from URLs whose default clone folder would be the current dir or the parent, which would make a later rm hit the wrong directory. This is the first of two guards (the second at :27 catches a basename that still contains a separator).","triggerScenarios":"Calling deriveCloneRepoNameFromUrl(url) or deriveValidatedClonePath({ url, destination }) with a URL like '.', '..', 'file:///', '/', a bare scheme with no path, an empty string, or a URL whose only segment after stripping .git reduces to the POSIX root.","commonSituations":"A user-typed or pasted clone URL that is incomplete ('https://github.com/'); a file:// URL pointing at a filesystem root; an empty URL from an unvalidated form field; programmatic clone flows where the URL was constructed by joining empty segments; WSL/host path edge cases that reduce to '/' after .git stripping.","solutions":["Validate the URL is non-empty and has a non-root path component before calling deriveCloneRepoNameFromUrl.","If the URL is user input, reject it at the form layer with a clear 'Enter a full repository URL' message.","For file:// URLs, ensure the path points at an actual repo directory, not a drive root.","Prefer deriveValidatedClonePath for end-to-end validation rather than calling deriveCloneRepoNameFromUrl directly."],"exampleFix":"// before\nderiveCloneRepoNameFromUrl(userInput) // userInput may be '' or 'file:///'\n\n// after: validate shape first\nif (!userInput || !/\\/[^/]+(\\/)?(\\.git)?$/.test(userInput)) {\n  throw new Error('Enter a full repository URL with an owner/repo path.')\n}\nderiveCloneRepoNameFromUrl(userInput)","handlingStrategy":"validation","validationCode":"function isValidCloneUrlShape(url: string): boolean {\n  if (!url) return false\n  const stripped = url.replace(/\\.git\\/?$/, '')\n  const base = /^[A-Za-z]:[\\\\/]/.test(stripped) || stripped.startsWith('\\\\\\\\')\n    ? stripped.split(/[\\\\/]/).pop() ?? ''\n    : stripped.split('/').pop() ?? ''\n  return Boolean(base) && base !== '.' && base !== '..'\n}\n\nif (!isValidCloneUrlShape(url)) throw new Error('Enter a clone URL with a non-empty repository name.')","typeGuard":"function isInvalidRepoName(error: unknown): boolean {\n  return error instanceof Error && error.message === 'Invalid repository name derived from URL'\n}","tryCatchPattern":"if (!isValidCloneUrlShape(url)) {\n  showFormError('clone-url', 'Enter a full repository URL (e.g. https://host/owner/repo).')\n  return\n}\ntry {\n  deriveCloneRepoNameFromUrl(url)\n} catch (error) {\n  if (isInvalidRepoName(error)) { showFormError('clone-url', 'That URL has no usable repository name.'); return }\n  throw error\n}","preventionTips":["Validate URL shape at the form layer so invalid input never reaches the main process.","Reject empty, '.', '..', and root-only URLs before calling deriveCloneRepoNameFromUrl.","Prefer deriveValidatedClonePath for end-to-end validation rather than the bare repo-name helper."],"tags":["git","clone","validation","path-traversal","input-validation"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}