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)
}