mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 00:03:45 +00:00
fix(runtime): refuse SSH hosts in project setup instead of acting locally
projectHostSetup.clone and .setupExistingFolder threaded executionHostId all the way down but never used it for routing: cloneRepo runs a local mkdir plus a local gitSpawn, and addRepo probes the path with existsSync/statSync. An `ssh:` host therefore cloned and validated on the *local* machine and then registered the result as living on the SSH host. It only failed loudly here because the remote path did not exist locally. With a plausible destination the clone succeeds and writes a setup record pointing at the wrong machine. Nothing legitimate sends `ssh:` to these RPCs: the renderer maps every ssh host (including ephemeral-VM `ssh:runtime-ssh-*`) to the desktop IPC path, which dispatches to addRemoteRepoFromPath/cloneRemoteRepo, and the IPC handler symmetrically rejects `runtime:`. Only the CLI can reach here with `ssh:`. Fail closed until the RPC learns to route through the SSH providers.
This commit is contained in:
@@ -7159,6 +7159,45 @@ describe('OrcaRuntimeService', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('refuses SSH hosts instead of setting the project up on the local machine', async () => {
|
||||
const spawnSpy = vi.spyOn(gitRunner, 'gitSpawn')
|
||||
const repos: Record<string, unknown>[] = []
|
||||
const runtimeStore = {
|
||||
...store,
|
||||
getRepos: () => [...repos] as never,
|
||||
addRepo: (repo: Record<string, unknown>) => {
|
||||
repos.push(repo)
|
||||
}
|
||||
}
|
||||
const runtime = new OrcaRuntimeService(runtimeStore as never)
|
||||
|
||||
try {
|
||||
await expect(
|
||||
runtime.setupProjectClone({
|
||||
projectId: 'github:stablyai/orca',
|
||||
hostId: 'ssh:openclaw',
|
||||
url: 'https://example.com/orca.git',
|
||||
destination: '/home/brennan'
|
||||
})
|
||||
).rejects.toThrow(/SSH hosts are not supported/)
|
||||
|
||||
await expect(
|
||||
runtime.setupProjectExistingFolder({
|
||||
projectId: 'github:stablyai/orca',
|
||||
hostId: 'ssh:openclaw',
|
||||
path: '/home/brennan/orca',
|
||||
kind: 'git'
|
||||
})
|
||||
).rejects.toThrow(/SSH hosts are not supported/)
|
||||
|
||||
// Why: the defect was a silent local clone/probe recorded as remote, not a bad message.
|
||||
expect(spawnSpy).not.toHaveBeenCalled()
|
||||
expect(repos).toHaveLength(0)
|
||||
} finally {
|
||||
spawnSpy.mockRestore()
|
||||
}
|
||||
})
|
||||
|
||||
it('adopts public clone repos into host-qualified project setup', async () => {
|
||||
const destination = await mkdtemp(join(tmpdir(), 'orca-runtime-project-clone-'))
|
||||
const clonePath = join(destination, 'orca')
|
||||
|
||||
@@ -1801,6 +1801,19 @@ function runtimeRepoMatchesExecutionHost(
|
||||
return repo.connectionId == null
|
||||
}
|
||||
|
||||
// Why: this runtime only has local git and local fs, so an ssh: host here would clone and
|
||||
// probe the wrong machine and then register the result as remote. SSH setup is owned by the
|
||||
// desktop IPC path (addRemoteRepoFromPath / cloneRemoteRepo), which the renderer routes to;
|
||||
// only `local` and `runtime:` legitimately reach these RPCs.
|
||||
function assertProjectHostSetupHostIsSupported(hostId: ExecutionHostId | null | undefined): void {
|
||||
if (parseExecutionHostId(hostId)?.kind !== 'ssh') {
|
||||
return
|
||||
}
|
||||
throw new Error(
|
||||
'SSH hosts are not supported by this operation. Set the project up from the Orca desktop app, which owns the SSH connection.'
|
||||
)
|
||||
}
|
||||
|
||||
function getRuntimeFolderWorkspaceInstanceId(repo: Repo, instanceId: string): string {
|
||||
return `${getRuntimeFolderWorkspaceRootId(repo)}${FOLDER_WORKSPACE_INSTANCE_SEPARATOR}${instanceId}`
|
||||
}
|
||||
@@ -14929,6 +14942,7 @@ export class OrcaRuntimeService {
|
||||
if (!this.store) {
|
||||
throw new Error('runtime_unavailable')
|
||||
}
|
||||
assertProjectHostSetupHostIsSupported(args.hostId)
|
||||
let repo = await this.addRepo(args.path, args.kind === 'folder' ? 'folder' : 'git', args.hostId)
|
||||
let setup = getProjectHostSetupForRepo(this.listProjectHostSetups(), repo)
|
||||
if (setup.projectId !== args.projectId) {
|
||||
@@ -14971,6 +14985,8 @@ export class OrcaRuntimeService {
|
||||
}
|
||||
|
||||
async setupProjectClone(args: ProjectHostSetupCloneArgs): Promise<ProjectHostSetupResult> {
|
||||
// Why: guard before cloneRepo, which would otherwise clone to the local disk.
|
||||
assertProjectHostSetupHostIsSupported(args.hostId)
|
||||
const repo = await this.cloneRepo(args.url, args.destination, args.hostId)
|
||||
return await this.setupProjectExistingFolder({
|
||||
projectId: args.projectId,
|
||||
|
||||
Reference in New Issue
Block a user