{"record":{"id":"ba77e04ed14dcd81","repo":"stablyai/orca","slug":"merge-request-fetch-remote-must-not-start-with","errorCode":null,"errorMessage":"Merge request fetch remote must not start with \"-\".","messagePattern":"Merge request fetch remote must not start with \"-\"\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/main/gitlab/mr-head-tracking-ref.ts","lineNumber":31,"sourceCode":"  wslDistro?: string\n}\n\n// Why: the relay's read-only git.exec channel rejects `fetch`, so SSH repos\n// must use the dedicated git.fetchGitLabMergeRequestHeadRef RPC. Mirrors\n// fetchGitHubPullRequestHeadRef so both providers pin the durable head ref\n// the same way.\nexport async function fetchGitLabMergeRequestHeadRef(\n  repo: { path: string; connectionId?: string | null },\n  sshGitProvider: SshGitProvider | null | undefined,\n  remote: string,\n  mrIid: number,\n  options: { localGitExecOptions?: LocalGitExecOptions } = {}\n): Promise<string> {\n  if (!isValidReviewHeadNumber(mrIid)) {\n    throw new Error(`Invalid merge request iid: ${String(mrIid)}`)\n  }\n  if (!isSafeReviewHeadFetchRemote(remote)) {\n    throw new Error('Merge request fetch remote must not start with \"-\".')\n  }\n  if (!repo.connectionId) {\n    const localGitExecOptions = options.localGitExecOptions ?? { cwd: repo.path }\n    const remoteComponent = await getReviewHeadRemoteComponent(remote, localGitExecOptions)\n    // Why: return the same path the fetch wrote so callers don't re-resolve identity.\n    const localRef = gitlabMergeRequestHeadLocalRef(remoteComponent, mrIid)\n    await gitExecFileAsync(\n      ['fetch', '--no-tags', remote, `+refs/merge-requests/${mrIid}/head:${localRef}`],\n      {\n        ...localGitExecOptions,\n        timeout: REVIEW_HEAD_FETCH_TIMEOUT_MS\n      }\n    )\n    return localRef\n  }\n  if (!sshGitProvider) {\n    throw new Error('SSH Git provider is not available. Reconnect to this target and try again.')\n  }","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/src/main/gitlab/mr-head-tracking-ref.ts#L13-L49","documentation":"Thrown by fetchGitLabMergeRequestHeadRef when isSafeReviewHeadFetchRemote(remote) is false — the remote name begins with '-', which git would interpret as a flag rather than a refspec argument (argument-injection guard). This protects the downstream `git fetch --no-tags <remote> ...` from a maliciously or accidentally malformed remote name.","triggerScenarios":"Passing a remote value that starts with '-' (e.g. '--upload-pack=...', '-o', or any dash-prefixed string) to fetchGitLabMergeRequestHeadRef. Can occur if remote is sourced from untrusted config, a URL query param, or a corrupted stored workspace state.","commonSituations":"Remote name read from an untrusted/configurable source without sanitization; a bug where an empty remote is replaced by a default flag string; test fixtures passing flag-like strings.","solutions":["Ensure the remote name is one of the actual remotes from `git remote` before calling.","Reject remote names starting with '-' at the input boundary (UI/config parser).","If the remote comes from stored state, validate it against the live `git remote` list on load."],"exampleFix":"// before\nawait fetchGitLabMergeRequestHeadRef(repo, ssh, userInputRemote, mrIid)\n// after\nconst safeRemote = /^[A-Za-z0-9][\\w.-]*$/.test(userInputRemote) ? userInputRemote : null\nif (!safeRemote) throw new Error(`Refusing unsafe remote name: ${userInputRemote}`)\nawait fetchGitLabMergeRequestHeadRef(repo, ssh, safeRemote, mrIid)","handlingStrategy":"validation","validationCode":"function isSafeRemoteName(remote: string): boolean {\n  return typeof remote === 'string' && remote.length > 0 && !remote.startsWith('-')\n}","typeGuard":"function isSafeReviewHeadFetchRemote(remote: unknown): remote is string {\n  return typeof remote === 'string' && remote.length > 0 && !remote.startsWith('-')\n}","tryCatchPattern":null,"preventionTips":["Source remote names only from `git remote` output, never from raw user/config input.","Reject any remote name starting with '-' at the input boundary.","Treat the remote as untrusted data; validate before interpolating into git args."],"tags":["git","security","injection","validation","remote"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}