Skip to content

Commit f9193c5

Browse files
author
nedtwigg
committed
Codex review R1: fix spec/code mismatch on paste tier order, escape \\r in shellEscapePosix
1 parent 375006f commit f9193c5

3 files changed

Lines changed: 8 additions & 4 deletions

File tree

docs/specs/mouse-and-clipboard.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -302,9 +302,9 @@ The bracketed-paste mode is read at paste time from xterm's public `terminal.mod
302302

303303
Paste reads the clipboard in three tiers, falling through in order:
304304

305-
1. **Plain text.** `navigator.clipboard.readText()`. If non-empty, the string is written to the PTY (with bracketed-paste wrapping when enabled by the inside program).
306-
2. **File references.** If the clipboard has no text but carries OS file references (Finder/Explorer Copy of a file), each path is shell-escaped and the space-joined list is written to the PTY with a trailing space so the next token starts cleanly.
307-
3. **Raw image data.** If neither of the above matches and the clipboard holds image bytes (e.g. a `Cmd+Shift+4` screenshot), the bytes are written to `$TMPDIR/mouseterm-drops/<uuid>.png` and that single path is pasted as in tier 2.
305+
1. **File references.** The platform adapter checks for OS file references (Finder/Explorer Copy of a file) via the sidecar/extension host. If present, each path is shell-escaped and the space-joined list is written to the PTY with a trailing space so the next token starts cleanly. Files are checked first so that a file-ref clipboard never reaches `navigator.clipboard.readText()` — on macOS WKWebView that call can trigger a native paste-permission popup when the clipboard came from another app.
306+
2. **Plain text.** `navigator.clipboard.readText()`. If non-empty, the string is written to the PTY (with bracketed-paste wrapping when enabled by the inside program).
307+
3. **Raw image data.** If neither of the above matches and the clipboard holds image bytes (e.g. a `Cmd+Shift+4` screenshot), the bytes are written to `$TMPDIR/mouseterm-drops/<uuid>.png` and that single path is pasted as in tier 1.
308308

309309
Each tier is implemented by a shared Node module (`standalone/sidecar/clipboard-ops.js`) that shells out to the OS-native clipboard tool: `osascript` on macOS, `Get-Clipboard` on Windows, `wl-paste`/`xclip` on Linux. The Tauri build reaches it through the existing sidecar; the VSCode build calls into the same module from its extension host. If every tier comes back empty, paste is a silent no-op.
310310

lib/src/lib/shell-escape.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ describe('shellEscapePosix', () => {
2626
expect(shellEscapePosix('a\\b.png')).toBe('a\\\\b.png');
2727
});
2828

29+
it('backslash-escapes carriage returns (security: prevents command injection)', () => {
30+
expect(shellEscapePosix('a\rb')).toBe('a\\\rb');
31+
});
32+
2933
it('backslash-escapes shell metacharacters', () => {
3034
expect(shellEscapePosix('a$b')).toBe('a\\$b');
3135
expect(shellEscapePosix('a`b')).toBe('a\\`b');

lib/src/lib/shell-escape.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ function detectIsWindows(): boolean {
1111
// metacharacter instead of wrapping in quotes. TUIs like `claude` recognize
1212
// backslash-escaped tokens as filesystem paths where a single-quoted whole
1313
// path gets treated as opaque pasted text.
14-
const POSIX_UNSAFE = /([ \t\n!"#$&'()*;<>?[\\\]`{|}~])/g;
14+
const POSIX_UNSAFE = /([ \t\n\r!"#$&'()*;<>?[\\\]`{|}~])/g;
1515

1616
export function shellEscapePosix(input: string): string {
1717
if (input === '') return "''";

0 commit comments

Comments
 (0)