{"record":{"id":"6566ba621867512c","repo":"stablyai/orca","slug":"access-denied-gitlab-source-host-does-not-match-r","errorCode":null,"errorMessage":"Access denied: GitLab source host does not match repository host","messagePattern":"Access denied: GitLab source host does not match repository host","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ipc/gitlab.ts","lineNumber":93,"sourceCode":"  }\n  const resolvedRepoPath = resolve(args.repoPath)\n  return store.getRepos().find((r) => resolve(r.path) === resolvedRepoPath)\n}\n\n// Why: mirror github.ts assertRegisteredRepo — main-process handlers\n// must never operate on a path the user hasn't explicitly registered as\n// a repo (filesystem-auth boundary). Source context adds a host check so a\n// task fetched from one machine cannot mutate a same-path repo on another.\nfunction assertRegisteredRepo(args: GitLabRepoSelectorArgs, store: Store): Repo {\n  const repo = findRegisteredGitLabRepo(args, store)\n  if (!repo) {\n    throw new Error('Access denied: unknown repository path')\n  }\n  if (\n    args.sourceContext?.provider === 'gitlab' &&\n    args.sourceContext.hostId !== getRepoExecutionHostId(repo)\n  ) {\n    throw new Error('Access denied: GitLab source host does not match repository host')\n  }\n  return repo\n}\n\nfunction repoConnectionId(repo: Repo): string | null {\n  return repo.connectionId ?? null\n}\n\nfunction localGitOptionArgs(store: Store, repo: Repo): [] | [LocalGitExecOptions] {\n  const localGitOptions = getLocalProjectWorktreeGitOptions(store, repo)\n  return localGitOptions.wslDistro ? [{ wslDistro: localGitOptions.wslDistro }] : []\n}\n\nfunction hostedReviewOptionArgs(store: Store, repo: Repo): [] | [HostedReviewExecutionOptions] {\n  const localGitOptions = getLocalProjectWorktreeGitOptions(store, repo)\n  return localGitOptions.wslDistro\n    ? [{ localGitExecOptions: { wslDistro: localGitOptions.wslDistro } }]\n    : []","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/src/main/ipc/gitlab.ts#L75-L111","documentation":"Thrown by assertRegisteredRepo in gitlab.ts:87 when the repo IS registered but the source-context host check fails: args.sourceContext.provider === 'gitlab' and args.sourceContext.hostId !== getRepoExecutionHostId(repo). This prevents a task fetched from one execution host from mutating a same-path repo on a different host.","triggerScenarios":"A GitLab operation carries sourceContext = { provider: 'gitlab', hostId: H1 } but the matched repo's execution host is H2 (different SSH target / different machine). The path matches a registered repo, but it belongs to a different host than the one the task originated from.","commonSituations":"Same repo path exists on two machines (e.g. both have ~/code/app); a GitLab task captured on host A is replayed on host B; SSH target changed/reconnected under a new hostId; stale sourceContext persisted across a host reconfiguration.","solutions":["Run the GitLab operation against the repo on the host recorded in sourceContext.hostId (the originating host).","If the host genuinely changed, update the repo's execution host association and refresh sourceContext.hostId to match getRepoExecutionHostId(repo).","Strip sourceContext when intentionally retargeting a task to a different host (do so only with explicit user intent).","Validate that the active connection/host matches the task's source host before dispatching the GitLab IPC call."],"exampleFix":"// before\nassertRegisteredRepo({ repoPath, sourceContext: { provider: 'gitlab', hostId: taskHostId } }, store)\n\n// after — only dispatch when host matches the registered repo's execution host\nconst repo = findRegisteredGitLabRepo({ repoPath }, store)\nif (getRepoExecutionHostId(repo) !== taskHostId) {\n  throw new Error(`retarget to host ${getRepoExecutionHostId(repo)} or drop sourceContext`)\n}\nassertRegisteredRepo({ repoPath, sourceContext: { provider: 'gitlab', hostId: taskHostId } }, store)","handlingStrategy":"validation","validationCode":"// Before a GitLab IPC call carrying sourceContext: confirm host alignment\nimport { getRepoExecutionHostId } from '../../shared/execution-host'\n\nfunction gitLabHostMatches(store, args) {\n  const repo = store.getRepos().find((r) => resolve(r.path) === resolve(args.repoPath))\n  if (!repo) return { kind: 'unregistered' }\n  if (args.sourceContext?.provider === 'gitlab' && args.sourceContext.hostId !== getRepoExecutionHostId(repo)) {\n    return { kind: 'host-mismatch', expected: getRepoExecutionHostId(repo), got: args.sourceContext.hostId }\n  }\n  return { kind: 'ok' }\n}","typeGuard":"export function hasGitLabSourceContext(\n  args: unknown\n): args is { repoPath: string; sourceContext: { provider: 'gitlab'; hostId: string } } {\n  return (\n    typeof args === 'object' && args !== null &&\n    (args as any).sourceContext?.provider === 'gitlab' &&\n    typeof (args as any).sourceContext?.hostId === 'string'\n  )\n}","tryCatchPattern":"try {\n  await ipcRenderer.invoke('gl:something', { repoPath, sourceContext })\n} catch (e) {\n  if (e instanceof Error && e.message === 'Access denied: GitLab source host does not match repository host') {\n    promptHostRetarget(sourceContext.hostId); return\n  }\n  throw e\n}","preventionTips":["Run GitLab operations on the host recorded in sourceContext.hostId; do not replay cross-host.","When the execution host genuinely changes, update the repo association and refresh sourceContext.hostId.","Strip sourceContext only when intentionally retargeting (explicit user intent).","Validate host alignment at the call site before dispatching GitLab IPC."],"tags":["security","authorization","gitlab","host-isolation","filesystem-auth"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}