Skip to content

fix: support Windows clipboard in remote HTTP terminals - #875

Draft
wesm wants to merge 4 commits into
mainfrom
remote-copy-paste
Draft

fix: support Windows clipboard in remote HTTP terminals#875
wesm wants to merge 4 commits into
mainfrom
remote-copy-paste

Conversation

@wesm

@wesm wesm commented Aug 10, 2026

Copy link
Copy Markdown
Member

Chrome users who open Forge through a plain-HTTP remote deployment could not reliably paste Windows clipboard text into a tmux workspace. xterm consumed Ctrl-V and Ctrl-Shift-V before Chrome could create a paste event. Copying from tmux also failed because the secure Clipboard API is unavailable on these origins, while Forge's server fallback is intentionally local-client-only.

Forge now leaves native paste shortcuts to the browser and consumes each resulting terminal paste event exactly once. Gesture-authorized terminal copy falls back to the browser's copy event when the modern Clipboard API is unavailable. On insecure origins, right-button mouse events stay out of tmux so Chrome's native context menu remains usable for paste.

The regression serves Forge through a non-loopback HTTP and WebSocket proxy, confirms the page is an insecure context without the async Clipboard API, and uses the real OS clipboard with trusted paste shortcuts. It verifies exact-once tmux input, tmux copy back to the OS clipboard, and no tmux mouse input from right click. The pinned Linux Chromium run exercises Ctrl-V and Ctrl-Shift-V directly.

Validation
  • Full frontend Vitest suite: 4,126 passed, 2 skipped.
  • Full affected Playwright file: 17 passed, 5 expected browser-specific skips across Chromium and Firefox.
  • Pinned Linux Chromium insecure-origin regression: 1 passed.
  • Frontend formatting, lint, kit-ui policy, Svelte checks, Effect diagnostics, context structure, privacy scans, and commit/push hooks passed.

Chrome users opening Forge over an HTTP Tailscale address could not reliably paste Windows clipboard text into a tmux workspace. Forge handled multiline paste itself but delegated the common single-line case to xterm, leaving two browser input paths with different behavior.

Make Forge own every non-empty text paste event, preserve the existing sanitizer and bracketed-paste mode, and stop xterm from sending the same event again. A real Chromium test now proves that the shortcut sends one payload through the terminal WebSocket and reaches tmux.

Generated with Codex
Co-authored-by: Codex <noreply@openai.com>
@roborev-ci

roborev-ci Bot commented Aug 10, 2026

Copy link
Copy Markdown

roborev: Combined Review (da727b6)

Review verdict: One high-severity test coverage gap remains; no security issues were identified.

High

  • frontend/tests/e2e-full/00-tmux-browser-clipboard.spec.ts:812 — The regression test runs on loopback in a secure browser context and explicitly grants Clipboard API permissions. The previous xterm-managed single-line paste path works under these conditions, so the test can pass without validating the insecure-HTTP fix or reproducing the reported remote-origin failure. Exercise paste through a non-loopback HTTP proxy, assert window.isSecureContext is false and the async Clipboard API is unavailable, then verify the shortcut produces exactly one terminal input.

Reviewers: 2 done | Synthesis: codex, 14s | Total: 4m46s

Chrome treats loopback as a secure context, so the prior regression could pass through xterm's Clipboard API path and miss the reported Tailscale HTTP failure.

Run the terminal through a non-loopback HTTP and WebSocket proxy. Exercise the cancelable browser paste-event boundary because headless Linux keyboard injection does not issue the native browser paste command. Verify Forge stops xterm's later handler and tmux receives one payload.

Generated with Codex
Co-authored-by: Codex <noreply@openai.com>
@wesm

wesm commented Aug 10, 2026

Copy link
Copy Markdown
Member Author

Addressed in 3ddbd5dde. The regression now runs through a non-loopback HTTP and WebSocket proxy, asserts window.isSecureContext === false and navigator.clipboard is unavailable, then sends clipboard data through a cancelable browser paste event. It verifies that Forge cancels the event before a later xterm-equivalent capture handler and that exactly one terminal input reaches tmux.

This intentionally starts at the ClipboardEvent boundary. Playwright key injection on headless Linux does not make Chromium execute the native browser paste command, even when the clipboard is populated and the xterm textarea is focused. The browser/OS owns shortcut invocation; the Forge regression now covers its full insecure-origin contract from the paste event through the real WebSocket and tmux flow.

@roborev-ci

roborev-ci Bot commented Aug 10, 2026

Copy link
Copy Markdown

roborev: Combined Review (3ddbd5d)

Code is clean: no Medium, High, or Critical findings were reported.


Reviewers: 2 done | Synthesis: codex, 6s | Total: 7m37s

@wesm

wesm commented Aug 10, 2026

Copy link
Copy Markdown
Member Author

tested and not working yet, so will keep at this

@mariusvniekerk
mariusvniekerk marked this pull request as draft August 10, 2026 20:42
Chrome users who open Forge through a plain-HTTP Tailscale address still need normal terminal clipboard behavior. xterm consumed paste shortcuts before Chrome could create a paste event, and remote clients could not use the local-server clipboard fallback when the secure Clipboard API was unavailable.

Keep native paste shortcuts browser-owned, provide a gesture-authorized copy-event fallback, and reserve insecure-origin right click for Chrome's context menu. The insecure-origin regression now exercises real clipboard paste and copy through tmux.

Generated with Codex
Co-authored-by: Codex <noreply@openai.com>
@wesm wesm changed the title fix: restore Windows clipboard paste in remote terminals fix: support Windows clipboard in remote HTTP terminals Aug 11, 2026
@roborev-ci

roborev-ci Bot commented Aug 11, 2026

Copy link
Copy Markdown

roborev: Combined Review (362b9ea)

The PR has one High-severity CI command-execution exposure and one Medium-severity macOS terminal-input regression.

High

  • frontend/tests/e2e-full/00-tmux-browser-clipboard.spec.ts:94 — The proxy exposes a live tmux WebSocket on a non-loopback interface and forwards arbitrary upgrade requests to the unauthenticated loopback-only e2e server. Anyone able to reach the CI container interface could enumerate workspace IDs, connect to /ws/v1/workspaces/{id}/terminal, and send shell commands. Restrict HTTP and upgrade handling to local-container clients, reject absolute-form request targets, and always construct requests against the fixed upstream.

Medium

  • frontend/src/lib/components/terminal/XtermTerminalPane.svelte:304 — The paste predicate intercepts both Ctrl+V and Meta+V on every platform. On macOS, Ctrl+V is terminal input (commonly quoted-insert), so it should reach the session as \x16. Use Meta+V on macOS and Ctrl+V elsewhere via the existing platform detection, and add coverage confirming macOS Ctrl+V reaches the terminal.

Reviewers: 2 done | Synthesis: codex, 14s | Total: 12m32s

The pane-move regression denied only the async Clipboard API. The new synchronous browser-copy path therefore succeeded before the test could observe its intended server fallback, which made the full E2E job fail after the product fix.

Deny both browser clipboard paths and stop coupling tmux command entry to exact terminal rendering bytes. The test still proves that keyboard focus survives the move and that OSC 52 reaches the forced fallback.

Generated with Codex
Co-authored-by: Codex <noreply@openai.com>
@roborev-ci

roborev-ci Bot commented Aug 11, 2026

Copy link
Copy Markdown

roborev: Combined Review (b0c9a0d)

No issues found.


Reviewers: 2 done | Synthesis: codex | Total: 11m9s

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant