Files
orca/src/relay/git-handler-fork-remote-exec.test.ts
T
Neil 080c95940f fix(ssh): let fork-PR worktrees add their contributor remote via the relay (#15827)
* fix(ssh): let fork-PR worktrees add their contributor remote via the relay

Creating a workspace from a fork PR on an SSH host failed with "Destructive
git remote operations are not allowed via exec". The relay's git.exec
allowlist blocked every `remote` write subcommand, but SSH fork-PR creation
has to run `git remote add <fork> <url>` on the host before it can fetch and
track the contributor's branch, so the whole create aborted.

Allow exactly the two shapes that flow needs -- `remote add <name> <url>` and
`remote remove <name>` -- validated with the same remote-name and URL rules
the relay already applies to every pushTarget-carrying RPC. Everything else
(set-url, rename, prune, extra operands, flags before the action) stays
blocked, and the URL must be a github.com clone/ssh URL, so no new reach is
granted beyond what push/fetch already accept.

`remote remove` was blocked too, which silently leaked fork remotes on SSH
hosts: worktree removal swallows the cleanup error. It works again now.

A host still running an older relay gets an actionable "reconnect to deploy
the latest relay" message instead of the raw policy error.

* test(git-exec): pin remote read/write mutation classification

Misclassifying `git remote` / `remote get-url` as mutating would flush the
relay and SSH provider git read caches on every remote probe, so pin both
directions.
2026-08-21 14:44:08 -07:00

65 lines
1.9 KiB
TypeScript

/**
* The relay's git.exec must carry the fork-PR remote setup that SSH worktree
* creation performs: adding the contributor's fork as a remote, and dropping it
* again when the last worktree using it is removed.
*/
import { describe, expect, it, beforeEach, afterEach } from 'vitest'
import { execFileSync } from 'node:child_process'
import { gitInit, type MockDispatcher } from './git-handler-test-setup'
import {
createGitHandlerRelay,
createGitTempDir,
removeGitTempDir
} from './git-handler-test-harness'
const FORK_REMOTE = 'pr-contributor-orca'
const FORK_URL = 'https://github.com/contributor/orca.git'
describe('GitHandler git.exec fork remote', () => {
let dispatcher: MockDispatcher
let tmpDir: string
beforeEach(() => {
tmpDir = createGitTempDir()
;({ dispatcher } = createGitHandlerRelay())
gitInit(tmpDir)
})
afterEach(async () => {
await removeGitTempDir(tmpDir)
})
function configuredRemotes(): string {
return execFileSync('git', ['remote'], { cwd: tmpDir, encoding: 'utf-8' }).trim()
}
it('adds and removes the fork remote', async () => {
await dispatcher.callRequest('git.exec', {
args: ['remote', 'add', FORK_REMOTE, FORK_URL],
cwd: tmpDir
})
expect(configuredRemotes()).toBe(FORK_REMOTE)
const url = (await dispatcher.callRequest('git.exec', {
args: ['remote', 'get-url', FORK_REMOTE],
cwd: tmpDir
})) as { stdout: string }
expect(url.stdout.trim()).toBe(FORK_URL)
await dispatcher.callRequest('git.exec', {
args: ['remote', 'remove', FORK_REMOTE],
cwd: tmpDir
})
expect(configuredRemotes()).toBe('')
})
it('still refuses to repoint an existing remote', async () => {
await expect(
dispatcher.callRequest('git.exec', {
args: ['remote', 'set-url', 'origin', FORK_URL],
cwd: tmpDir
})
).rejects.toThrow('Destructive git remote operations are not allowed via exec')
})
})