diff --git a/src/main/ssh/legacy-relay-bridge/legacy-relay-cli-call-limits.test.ts b/src/main/ssh/legacy-relay-bridge/legacy-relay-cli-call-limits.test.ts index e807e87aa7c0..435a608eb791 100644 --- a/src/main/ssh/legacy-relay-bridge/legacy-relay-cli-call-limits.test.ts +++ b/src/main/ssh/legacy-relay-bridge/legacy-relay-cli-call-limits.test.ts @@ -188,6 +188,17 @@ describe('limits on relay-era CLI calls', () => { expect(body(replay).result.message).toEqual(body(first).result.message) }) + it('bounds the rows an inbox read may return', async () => { + const read = vi.spyOn(db, 'getAllMessagesForHandle') + for (const limit of ['-1', '1000000000', '0']) { + await server().cli( + { argv: ['orchestration', 'inbox', '--limit', limit, '--json'], env: ENV }, + new AbortController().signal + ) + } + expect(read.mock.calls.map((call) => call[1])).toEqual([1, 100, 1]) + }) + it('names nobody when two hosts claim one handle', () => { const terminal = { handle: WORKER, paneKey: WORKER_PANE, processIncarnation: 'p-1' } const fresh = new OrcaRuntimeService() diff --git a/src/main/ssh/legacy-relay-bridge/legacy-relay-cli-call-limits.ts b/src/main/ssh/legacy-relay-bridge/legacy-relay-cli-call-limits.ts index 90a2aded53a7..631fadb53a05 100644 --- a/src/main/ssh/legacy-relay-bridge/legacy-relay-cli-call-limits.ts +++ b/src/main/ssh/legacy-relay-bridge/legacy-relay-cli-call-limits.ts @@ -36,6 +36,13 @@ export function optionalAskTimeout(timeoutMs: number | undefined): number | unde return timeoutMs === undefined ? undefined : clampOrchestrationAskTimeoutMs(timeoutMs) } +/** The inbox handler passes `limit` straight to SQL, where a negative one means "every row". */ +const MAX_INBOX_ROWS = 100 + +export function cappedInboxLimit(limit: number | undefined): number | undefined { + return limit === undefined ? undefined : Math.min(Math.max(1, Math.floor(limit)), MAX_INBOX_ROWS) +} + export const LEGACY_RELAY_CLI_BUSY = 'This host already has too many older-terminal orca commands waiting on this desktop. ' + 'Let some finish, then try again.' diff --git a/src/main/ssh/legacy-relay-bridge/legacy-relay-cli-caller.ts b/src/main/ssh/legacy-relay-bridge/legacy-relay-cli-caller.ts index ec9cb6b048f4..e984b1bcc88b 100644 --- a/src/main/ssh/legacy-relay-bridge/legacy-relay-cli-caller.ts +++ b/src/main/ssh/legacy-relay-bridge/legacy-relay-cli-caller.ts @@ -48,6 +48,7 @@ export function resolveLegacyRelayCliCaller( runtime: OrcaRuntimeService, env: Readonly>, livePane: (paneKey: string) => LegacyRelayLivePane | null, + targetId: string, /** A retry of a mutation this caller already completed, which may outlive its Dispatch. */ replay?: { fingerprint: string; requestId: string } ): LegacyRelayCliCaller { @@ -76,7 +77,7 @@ export function resolveLegacyRelayCliCaller( const replayDispatch = replay ? own(settledLegacyDispatch(db, handle, paneKey, replay.requestId)) : undefined - const run = resolveCoordinatedRun(db, handle, paneKey) + const run = resolveCoordinatedRun(db, handle, paneKey, targetId) if (!dispatch && !replayDispatch && !run) { throw new Error(LEGACY_RELAY_CLI_UNVERIFIED) } @@ -123,7 +124,8 @@ function settledLegacyDispatch( function resolveCoordinatedRun( db: OrchestrationDb, handle: string, - paneKey: string + paneKey: string, + targetId: string ): RunRow | undefined { const bound = db.getCurrentRunForPane(paneKey) if (bound) { @@ -135,11 +137,29 @@ function resolveCoordinatedRun( principal?.status === 'committed' && principal.terminal_handle === handle && samePane(principal.pane_key, paneKey) && + principalHostTargetId(principal.host_scope) === targetId && db.resolveLegacyCoordinatorCandidate({ runId: adopted, terminalHandle: handle, paneKey }) ? db.getRun(adopted) : undefined } +/** The SSH target a committed principal attested from; a pane key alone is not unique per host. */ +function principalHostTargetId(hostScope: string): string | undefined { + try { + const parsed: unknown = JSON.parse(hostScope) + return parsed && + typeof parsed === 'object' && + 'kind' in parsed && + parsed.kind === 'ssh' && + 'targetId' in parsed && + typeof parsed.targetId === 'string' + ? parsed.targetId + : undefined + } catch { + return undefined + } +} + function outside(subject: string): Error { return new Error( `A terminal started by an older version of Orca can reach only its own orchestration, and ${subject} is outside it.` diff --git a/src/main/ssh/legacy-relay-bridge/legacy-relay-cli-legacy-records.test.ts b/src/main/ssh/legacy-relay-bridge/legacy-relay-cli-legacy-records.test.ts index 66d9591f9938..893350858941 100644 --- a/src/main/ssh/legacy-relay-bridge/legacy-relay-cli-legacy-records.test.ts +++ b/src/main/ssh/legacy-relay-bridge/legacy-relay-cli-legacy-records.test.ts @@ -153,6 +153,22 @@ describe('the relay-era orca CLI on migrated legacy orchestration records', () = expect(database.getLegacyCoordinatorPrincipal(adoptedRunId)).toBeUndefined() }) + it('refuses a legacy coordinator whose principal was attested from another host', async () => { + const { runtime, database } = legacyRuntime() + database.commitLegacyCompatibilityPrincipal({ + runId: database.getLegacyAdoption()?.adopted_run_id ?? '', + role: 'coordinator', + hostScope: JSON.stringify({ kind: 'ssh', targetId: 'another-host' }), + terminalHandle: COORDINATOR, + paneKey: COORDINATOR_PANE, + launchTokenHash: createHash('sha256').update('coordinator-token').digest('hex'), + processIncarnation: `${COORDINATOR_PTY}:inc-2` + }) + await expect( + run(runtime, ['orchestration', 'check', '--json'], coordinatorEnv()) + ).rejects.toThrow(LEGACY_RELAY_CLI_UNVERIFIED) + }) + it('admits a legacy coordinator bound to this pane, and the worker reports to it', async () => { const { runtime, database, taskId, dispatchId } = legacyRuntime() database.commitLegacyCompatibilityPrincipal({ diff --git a/src/main/ssh/legacy-relay-bridge/legacy-relay-cli-server.ts b/src/main/ssh/legacy-relay-bridge/legacy-relay-cli-server.ts index 3ea0cb5e31ef..9b2c4311173a 100644 --- a/src/main/ssh/legacy-relay-bridge/legacy-relay-cli-server.ts +++ b/src/main/ssh/legacy-relay-bridge/legacy-relay-cli-server.ts @@ -28,6 +28,7 @@ import { } from './legacy-relay-cli-args' import type { LegacyRelayCliServer } from './legacy-relay-client' import { + cappedInboxLimit, cappedWait, isExactLegacyRelayCliReplay, optionalAskTimeout, @@ -126,7 +127,7 @@ function buildCall( case 'orchestration inbox': return { method: 'orchestration.inbox', - params: { ...self, limit: numberFlag(flags, 'limit') } + params: { ...self, limit: cappedInboxLimit(numberFlag(flags, 'limit')) } } case 'orchestration send': return { @@ -246,7 +247,7 @@ function admit( } const fingerprint = callerFingerprint(deps.targetId, env) const replay = replayRequestId ? { fingerprint, requestId: replayRequestId } : undefined - const caller = resolveLegacyRelayCliCaller(runtime, env, deps.livePane, replay) + const caller = resolveLegacyRelayCliCaller(runtime, env, deps.livePane, deps.targetId, replay) return { runtime, caller, env, fingerprint, signal } } diff --git a/src/renderer/src/components/settings/SshHostAdvancedFields.tsx b/src/renderer/src/components/settings/SshHostAdvancedFields.tsx index dea5cc2badaf..cb8228331698 100644 --- a/src/renderer/src/components/settings/SshHostAdvancedFields.tsx +++ b/src/renderer/src/components/settings/SshHostAdvancedFields.tsx @@ -126,7 +126,7 @@ export function SshHostAdvancedFields({

{translate( 'auto.components.settings.SshTargetForm.allowRemoteCliControlReset', - 'Orca turned this off after an update, because it now lets this host drive your desktop browser. Turn it on again only if you want that.' + 'Orca turned this off after an update, because it now lets this host drive your desktop browser and lets terminals started by an older Orca on this host run orchestration commands. Turn it on again only if you want that.' )}

) : null} diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 28b53c78f91a..6ee072cb03de 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -8993,7 +8993,7 @@ "53e9aabfc0": "Uses OpenSSH multiplexing when available. Turn off for hosts with custom SSH restrictions.", "allowRemoteCliControl": "Allow this host's orca CLI to control Orca", "allowRemoteCliControlHelp": "When on, orca commands run on this host can open and drive Orca browser tabs on this desktop, only in this host's workspaces and only while it is connected. Terminals started by an older Orca on this host can also run orchestration commands (check, inbox, send, ask, reply) for their own supervised work. They cannot read your files, clipboard or cookies. When off, they reach only this host's own Orca server.", - "allowRemoteCliControlReset": "Orca turned this off after an update, because it now lets this host drive your desktop browser. Turn it on again only if you want that.", + "allowRemoteCliControlReset": "Orca turned this off after an update, because it now lets this host drive your desktop browser and lets terminals started by an older Orca on this host run orchestration commands. Turn it on again only if you want that.", "editTitle": "Edit SSH host", "addTitle": "Add SSH host", "editDescription": "Update connection details for this machine. Changes apply on next connect.", diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index 201372c5e9a5..c37de422b0e2 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -8720,7 +8720,7 @@ "editingPrefix": "Editando", "allowRemoteCliControl": "Permitir que la orca CLI de este host controle Orca", "allowRemoteCliControlHelp": "Cuando está activado, los comandos orca ejecutados en este host pueden abrir y controlar pestañas del navegador de Orca en este escritorio, solo en los espacios de trabajo de este host y solo mientras esté conectado. Las terminales iniciadas por una versión anterior de Orca en este host también pueden ejecutar comandos de orquestación (check, inbox, send, ask, reply) para su propio trabajo supervisado. No pueden leer tus archivos, tu portapapeles ni tus cookies. Cuando está desactivado, solo llegan al propio servidor de Orca de este host.", - "allowRemoteCliControlReset": "Orca lo desactivó tras una actualización, porque ahora permite que este host controle el navegador de tu escritorio. Vuelve a activarlo solo si lo deseas." + "allowRemoteCliControlReset": "Orca lo desactivó tras una actualización, porque ahora permite que este host controle el navegador de tu escritorio y que las terminales iniciadas por una versión anterior de Orca en este host ejecuten comandos de orquestación. Vuelve a activarlo solo si lo deseas." }, "TasksPane": { "f71d8a9dd3": "Proveedores de tareas", diff --git a/src/renderer/src/i18n/locales/fr.json b/src/renderer/src/i18n/locales/fr.json index 3fd126395c86..66e7b53ce0e9 100644 --- a/src/renderer/src/i18n/locales/fr.json +++ b/src/renderer/src/i18n/locales/fr.json @@ -8870,7 +8870,7 @@ "editingPrefix": "Modification", "allowRemoteCliControl": "Autoriser l'orca CLI de cet hôte à contrôler Orca", "allowRemoteCliControlHelp": "Lorsque cette option est activée, les commandes orca exécutées sur cet hôte peuvent ouvrir et piloter des onglets du navigateur Orca sur ce bureau, uniquement dans les espaces de travail de cet hôte et uniquement tant qu'il est connecté. Les terminaux lancés par une ancienne version d'Orca sur cet hôte peuvent aussi exécuter des commandes d'orchestration (check, inbox, send, ask, reply) pour leur propre travail supervisé. Elles ne peuvent pas lire vos fichiers, votre presse-papiers ni vos cookies. Lorsqu'elle est désactivée, elles n'atteignent que le serveur Orca de cet hôte.", - "allowRemoteCliControlReset": "Orca l'a désactivé après une mise à jour, car il permet désormais à cet hôte de piloter le navigateur de votre bureau. Réactivez-le seulement si vous le souhaitez." + "allowRemoteCliControlReset": "Orca l'a désactivé après une mise à jour, car il permet désormais à cet hôte de piloter le navigateur de votre bureau et aux terminaux lancés par une ancienne version d'Orca sur cet hôte d'exécuter des commandes d'orchestration. Réactivez-le seulement si vous le souhaitez." }, "TasksPane": { "f71d8a9dd3": "Fournisseurs de tâches", diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index dc4055dc69c2..ffb82c956771 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -8746,7 +8746,7 @@ "editingPrefix": "編集中", "allowRemoteCliControl": "このホストの orca CLI による Orca の操作を許可する", "allowRemoteCliControlHelp": "オンにすると、このホストで実行される orca コマンドは、このホストのワークスペース内に限り、接続中のみ、このデスクトップの Orca ブラウザのタブを開いて操作できます。このホストで古い Orca が起動したターミナルも、自身の監督下の作業についてオーケストレーションコマンド(check、inbox、send、ask、reply)を実行できます。ファイル、クリップボード、Cookie は読み取れません。オフの場合、このホスト自身の Orca サーバーにのみアクセスします。", - "allowRemoteCliControlReset": "アップデート後に Orca がこれをオフにしました。この設定でこのホストがデスクトップのブラウザを操作できるようになったためです。必要な場合のみ再度オンにしてください。" + "allowRemoteCliControlReset": "アップデート後に Orca がこれをオフにしました。この設定でこのホストがデスクトップのブラウザを操作でき、このホストで古い Orca が起動したターミナルがオーケストレーションコマンドを実行できるようになったためです。必要な場合のみ再度オンにしてください。" }, "TasksPane": { "f71d8a9dd3": "タスクプロバイダー", diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index 1a62fb3348b6..74f9ab887a10 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -8709,7 +8709,7 @@ "editingPrefix": "편집 중", "allowRemoteCliControl": "이 호스트의 orca CLI가 Orca를 제어하도록 허용", "allowRemoteCliControlHelp": "켜면 이 호스트에서 실행한 orca 명령이 이 호스트의 작업 공간에서만, 연결되어 있는 동안에만 이 데스크톱의 Orca 브라우저 탭을 열고 조작할 수 있습니다. 이 호스트에서 이전 버전의 Orca가 시작한 터미널도 자신이 감독받는 작업에 대해 오케스트레이션 명령(check, inbox, send, ask, reply)을 실행할 수 있습니다. 파일, 클립보드, 쿠키는 읽을 수 없습니다. 끄면 이 호스트 자체의 Orca 서버에만 접근합니다.", - "allowRemoteCliControlReset": "업데이트 후 Orca가 이 설정을 껐습니다. 이제 이 호스트가 데스크톱 브라우저를 조작할 수 있게 되었기 때문입니다. 원하는 경우에만 다시 켜세요." + "allowRemoteCliControlReset": "업데이트 후 Orca가 이 설정을 껐습니다. 이제 이 호스트가 데스크톱 브라우저를 조작할 수 있고, 이 호스트에서 이전 버전의 Orca가 시작한 터미널이 오케스트레이션 명령을 실행할 수 있게 되었기 때문입니다. 원하는 경우에만 다시 켜세요." }, "TasksPane": { "f71d8a9dd3": "작업 제공자", diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index 948d63a0ae2a..9b3daacc62af 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -8709,7 +8709,7 @@ "editingPrefix": "正在编辑", "allowRemoteCliControl": "允许此主机的 orca CLI 控制 Orca", "allowRemoteCliControlHelp": "开启后,在此主机上运行的 orca 命令可以打开并操作此桌面上的 Orca 浏览器标签页,但仅限此主机的工作区,且仅在主机连接期间。此主机上由旧版 Orca 启动的终端也可以为其自身受监督的工作运行编排命令(check、inbox、send、ask、reply)。它们无法读取你的文件、剪贴板或 Cookie。关闭时,它们只能访问此主机自身的 Orca 服务器。", - "allowRemoteCliControlReset": "更新后 Orca 关闭了此项,因为它现在允许此主机操作你桌面上的浏览器。仅在需要时再重新开启。" + "allowRemoteCliControlReset": "更新后 Orca 关闭了此项,因为它现在允许此主机操作你桌面上的浏览器,并允许此主机上由旧版 Orca 启动的终端运行编排命令。仅在需要时再重新开启。" }, "TasksPane": { "f71d8a9dd3": "任务提供者",