diff --git a/apps/desktop/src/plugins/hermes-bots/screen-pane-clipboard.test.tsx b/apps/desktop/src/plugins/hermes-bots/screen-pane-clipboard.test.tsx index 88579f6566..ad8eec3dce 100644 --- a/apps/desktop/src/plugins/hermes-bots/screen-pane-clipboard.test.tsx +++ b/apps/desktop/src/plugins/hermes-bots/screen-pane-clipboard.test.tsx @@ -144,3 +144,15 @@ it('drops a paste while this viewer only watches (no lease)', async () => { expect(rfbs[0].clipboardPasteFrom).not.toHaveBeenCalled() view.unmount() }) + +it('drops a paste over the bridge\'s 256 KiB cut-text cap instead of forwarding it', async () => { + const view = render() + await waitFor(() => expect(rfbs).toHaveLength(1)) + await waitFor(() => expect(rfbs[0].viewOnly).toBe(false)) + + const oversized = 'a'.repeat(256 * 1024 + 1) + fireEvent.paste(rfbs[0].target, { clipboardData: { getData: () => oversized } }) + + expect(rfbs[0].clipboardPasteFrom).not.toHaveBeenCalled() + view.unmount() +}) diff --git a/apps/desktop/src/plugins/hermes-bots/screen-pane.tsx b/apps/desktop/src/plugins/hermes-bots/screen-pane.tsx index 9841545000..744c6483f5 100644 --- a/apps/desktop/src/plugins/hermes-bots/screen-pane.tsx +++ b/apps/desktop/src/plugins/hermes-bots/screen-pane.tsx @@ -62,6 +62,9 @@ const CLOSE_CONTROL_TAKEN = 4000 /** Evictions arriving this soon after dialing count toward the loop budget; slower ones reset it. */ const EVICTION_LOOP_WINDOW_MS = 10_000 const MAX_RAPID_EVICTIONS = 3 +/** Mirrors tools/bot_desktop/rfb_filter.py's _MAX_CUT_TEXT: the bridge closes the display + * socket on any ClientCutText over this, so an oversized paste must never reach the client. */ +const MAX_PASTE_CUT_TEXT = 256 * 1024 async function loadRfb(): Promise< new (target: HTMLElement, socket: WebSocket, options?: Record) => RfbLike @@ -293,7 +296,7 @@ export function BotScreenPane({ bot }: { bot: RosterRow }) { const text = event.clipboardData?.getData('text') - if (text) { + if (text && text.length <= MAX_PASTE_CUT_TEXT) { event.preventDefault() client.clipboardPasteFrom(text) }