Repository navigation
fix(desktop): paste clipboard images on Linux - #865
Conversation
[Medium impact] The change reaches image pastes in the Linux desktop chat composer. Fix the unsafe temporary-file writes and wrong-chat attachment behavior before merging. Findings
Fix with agent prompt### Issue 1
src/commands/app.rs:1473-1476
If the temporary directory is shared with other local users, another user can create `openresearch-clipboard` first and plant a `<pid>-1.png` symlink to a file the desktop user can write. The next image paste follows that symlink and replaces the target with PNG bytes. With a `022` umask, new clipboard files are also readable by other local users.
Use a private, randomly named directory with owner-only permissions and create each file exclusively.
**How this was verified:** The paste request reaches `std::fs::write` through an existing shared directory without checking its owner, rejecting symlinks, or creating the destination exclusively.
### Issue 2
ui/src/components/ChatPanel.tsx:5047-5054
Switching chats while the native request or image download is pending can attach the image to the newly selected chat. `addFiles` reads `composerScopeRef.current` only after both awaits, so its later check accepts that new chat. The callback still holds the old render's `stashKey`, which can also disagree with the captured scope.
Capture the originating composer before the first await and keep the image tied to it through the download and file read.
### Issue 3
ui/src/desktopClipboard.ts:40-42
A native reply that arrives after the three-second timeout leaves its PNG behind. The timeout removes the pending request, so this handler returns before the file can reach the normal download-and-delete flow.
Delete the returned temporary file when a late reply has no waiting request. The startup sweep only runs on another launch and skips files less than an hour old, so it does not reliably clean up these failed pastes.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.SummaryAdds a Linux-only GTK fallback when WebKit cannot provide clipboard image bytes.
|
| let dir = std::env::temp_dir().join("openresearch-clipboard"); | ||
| std::fs::create_dir_all(&dir).map_err(|error| error.to_string())?; | ||
| let path = dir.join(format!("{}-{id}.png", std::process::id())); | ||
| std::fs::write(&path, &bytes).map_err(|error| error.to_string())?; |
There was a problem hiding this comment.
Clipboard writes can overwrite files
If the temporary directory is shared with other local users, another user can create openresearch-clipboard first and plant a <pid>-1.png symlink to a file the desktop user can write. The next image paste follows that symlink and replaces the target with PNG bytes. With a 022 umask, new clipboard files are also readable by other local users.
Use a private, randomly named directory with owner-only permissions and create each file exclusively.
How this was verified: The paste request reaches std::fs::write through an existing shared directory without checking its owner, rejecting symlinks, or creating the destination exclusively.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/commands/app.rs
Line: 1473-1476
Comment:
**Clipboard writes can overwrite files**
If the temporary directory is shared with other local users, another user can create `openresearch-clipboard` first and plant a `<pid>-1.png` symlink to a file the desktop user can write. The next image paste follows that symlink and replaces the target with PNG bytes. With a `022` umask, new clipboard files are also readable by other local users.
Use a private, randomly named directory with owner-only permissions and create each file exclusively.
**How this was verified:** The paste request reaches `std::fs::write` through an existing shared directory without checking its owner, rejecting symlinks, or creating the destination exclusively.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| void (async () => { | ||
| const payload = await requestDesktopClipboardPaste(); | ||
| if (!payload) { | ||
| return; | ||
| } | ||
| const file = await fileFromDesktopClipboardPayload(payload); | ||
| if (file) { | ||
| addFiles([file]); |
There was a problem hiding this comment.
Switching chats while the native request or image download is pending can attach the image to the newly selected chat. addFiles reads composerScopeRef.current only after both awaits, so its later check accepts that new chat. The callback still holds the old render's stashKey, which can also disagree with the captured scope.
Capture the originating composer before the first await and keep the image tied to it through the download and file read.
Prompt To Fix With AI
This is a comment left during a code review.
Path: ui/src/components/ChatPanel.tsx
Line: 5047-5054
Comment:
**Images reach the wrong chat**
Switching chats while the native request or image download is pending can attach the image to the newly selected chat. `addFiles` reads `composerScopeRef.current` only after both awaits, so its later check accepts that new chat. The callback still holds the old render's `stashKey`, which can also disagree with the captured scope.
Capture the originating composer before the first await and keep the image tied to it through the download and file read.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| const pending = pendingPastes.get(response.id); | ||
| if (!pending) { | ||
| return; |
There was a problem hiding this comment.
Late replies leave screenshots behind
A native reply that arrives after the three-second timeout leaves its PNG behind. The timeout removes the pending request, so this handler returns before the file can reach the normal download-and-delete flow.
Delete the returned temporary file when a late reply has no waiting request. The startup sweep only runs on another launch and skips files less than an hour old, so it does not reliably clean up these failed pastes.
Prompt To Fix With AI
This is a comment left during a code review.
Path: ui/src/desktopClipboard.ts
Line: 40-42
Comment:
**Late replies leave screenshots behind**
A native reply that arrives after the three-second timeout leaves its PNG behind. The timeout removes the pending request, so this handler returns before the file can reach the normal download-and-delete flow.
Delete the returned temporary file when a late reply has no waiting request. The startup sweep only runs on another launch and skips files less than an hour old, so it does not reliably clean up these failed pastes.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.|
thanks for chasing this down, the diagnosis is right: on WebKitGTK 2.50/2.52 a pasted image arrives with tried with a separate process owning a real
blocking: this is a lot of surface (the GTK clipboard path, temp dir, ipc messages, non-blocking: on 2.54.1 the existing not verified: we only tested in VMs, and the 2.50.4 run used the AppImage's bundled WebKit but the host's GTK 3.24, not the image's 22.04 GTK. so we're trusting your own test on real hardware and in the real AppImage. plain-text clipboards weren't measured on 2.50/2.52 either, so check the clipboard before |
|
tldr pls simplify the patch but i also am really curious on the environment you hit this in. |
Use a Linux-only native GTK clipboard fallback when WebKit exposes an image paste with empty types or a zero-byte file.
Store clipboard images in the user cache with private permissions and exclusive file creation. Keep pending pastes tied to their originating composer and clean up late native replies.
Read clipboard images with navigator.clipboard.read() inside the composer paste handler and remove the Linux GTK bridge, IPC messages, and temporary files. Capture the target composer scope before the async read so a paste cannot land in a different chat.
77a6514 to
4ffbf0a
Compare
|
For sonnet comments: Environment: CachyOS, KDE Plasma Wayland, official AppImage 0.2.24 (bundled WebKitGTK 2.50.4, host GTK 3.24.52). Also tested system WebKitGTK 2.54.2. The clipboard image came from Spectacle. On 2.50.4, screenshot + Ctrl+V attaches, so #864 doesn't reproduce. On 2.54.2, a trusted WebDriver paste had Text-only paste still works normally. Image+text is treated as an image. Copying a PNG file from the file manager still pastes its UI tests, build, clippy and CI are green. For @paperclover |

Closes #864
Summary
typeslist or a zero-byteFile.Test plan
npx --yes pnpm@12.16.1 test- 573 passedcargo test --locked --features desktop- 1353 passedcargo clippy --all-targets --locked --features desktop -- -D warningscargo build --release --locked --features desktop