Skip to content

Ask before a terminal reads the clipboard and share plain text only - #15136

Closed
austinywang wants to merge 3 commits into
mainfrom
terminal-clipboard-read-guard
Closed

austinywang wants to merge 3 commits into
mainfrom
terminal-clipboard-read-guard

Conversation

@austinywang

@austinywang austinywang commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Two gaps in how cmux answers clipboard reads that a terminal program starts (for example with OSC 52), rather than a paste the user makes:

  • Reads were approved without asking. Ghostty's default is clipboard-read = ask, but cmux answered Ghostty's confirmation callback for a read by approving it, so the program received the clipboard with no prompt. Now a read always asks in a sheet on the terminal's window, and is rejected when there is no window to ask in. The sheet says "Allow Clipboard Access?" with Allow and Deny, not the unsafe-paste wording. clipboard-read = allow and deny are enforced inside Ghostty and never reach this code, so allow still gives silent reads to anyone who sets it. terminal.confirmUnsafePaste still only governs pastes.
  • Reads ran the full paste preparation. A read went through the same .paste preparation as Cmd+V, so Finder files and images on the pasteboard could be saved, and for ssh, detected-ssh and relay panes uploaded, before the read was confirmed. Only the cloud-image branch checked for a paste gesture. Now a read the terminal program started takes the pasteboard's plain-text flavor only and never prepares, saves or uploads files or images, for any pane kind. It also no longer becomes input to a remote tmux mirror pane.

Native paste is unchanged: key binding, Paste menu item, bracketed paste and middle click still get the whole pasteboard, including image and file upload. The two cases are told apart by the existing input admission: a request registered inside a paste gesture's intent is reserved, and anything else is unsequenced. That distinction is now RuntimeClipboardReadContent in CmuxTerminalCore. TerminalImageTransferMode gains a .plainText case, handled in the off-main preparation worker.

Testing

The failing tests were committed first (41978d7), then the fix (aef9ab3). The same focused command ran red and green:

swift test --package-path Packages/macOS/CmuxTerminalCore --filter 'TerminalUnsafePasteConfirmationPolicyTests|RuntimeClipboardReadContentTests'
  • Red at 41978d7: 10 tests in 2 suites, 3 failed with 5 issues. A read got .approve where the tests expect .askInWindowSheet (window) and .reject (no window), whatever the setting. A read with no paste intent got .pasteboard where .plainText was expected.
  • Green at aef9ab3: 10 tests in 2 suites passed. The native-paste test (registered inside withRuntimeClipboardPasteIntent, so it keeps .pasteboard) passed on both sides. So did the existing unsafe-paste policy tests.

swift test --package-path Packages/macOS/CmuxTerminal --filter PasteboardTextContentsTests gave 8 passed. That includes the new plainTextFlavorNeverReadsFilesOrImages, which checks that the plain-text reader returns nothing for a file-URL-only or PNG-only pasteboard, writes no temporary file, and returns .string text. It characterizes existing behavior the fix relies on, so it would also pass on main.

python3 scripts/verify-local.py --affected origin/main --swift-changed origin/main passed 5/5: swift-syntax (8 files), xcstrings, localization, package-groups and feature-flags.

Not run locally:

  • The app target and cmuxTests. Nothing under Sources/ was compiled or run locally, including the new .plainText branch in TerminalImageTransferPlanner, the read callback wiring and the mirror-pane gate. CI covers the app build and app tests.
  • No tagged build was dogfooded, so the new sheet wording and the end-to-end prompt under clipboard-read = ask are unverified live.
  • The allow/deny side is Ghostty's and has no cmux test.

Localization: 3 new keys (terminal.clipboardReadConfirmation.title, .message, .deny), translated into all 8 non-English catalog locales. The Allow button reuses common.allow. scripts/localization_catalog.py check reports 0 parity errors.

Changelog

Fixed: terminal clipboard reads ask before sharing the clipboard and never upload files

Checklist

  • Behavior changes have added or updated tests, or Testing says why not
  • UI, settings, menu, schema, help-text or user-facing docs change: localization audited, and the result is stated above
  • Reviewed with a subagent before merge (cmux-review), and all bot and human review comments resolved

🤖 Generated with Claude Code


Summary by cubic

Fixes two gaps in how cmux answers clipboard reads a terminal program starts (for example with OSC 52): reads were approved without asking, and ran the full paste-preparation path.

  • Reads now always ask in a window sheet with Allow/Deny wording, and are rejected when there is no window to ask in; terminal.confirmUnsafePaste still only governs pastes.
  • Reads take the pasteboard's plain-text flavor only, so they never save or upload Finder files or images for any pane kind, and never become input to a remote tmux mirror pane.
  • Native paste gestures (key binding, Paste menu, bracketed paste, middle click) keep the whole pasteboard, including image and file upload.

Written for commit 2a2e771. Summary will update on new commits.

Review in cubic

austinywang and others added 2 commits September 27, 2026 20:40
A clipboard read the terminal program starts should ask in a window
sheet whatever the unsafe-paste setting, and reject when there is no
window. It should get the pasteboard's plain text only, while a native
paste keeps files and images.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A clipboard read the terminal program starts (for example OSC 52 under
Ghostty's default clipboard-read = ask) now always asks in a window sheet,
and is rejected when there is no window, instead of being approved unasked.
The sheet uses clipboard access wording rather than the paste wording.

Such a read also takes only the pasteboard's plain-text flavor. It never
prepares, saves or uploads Finder files or images, for any pane kind, and
never becomes input to a remote tmux mirror pane. Native paste gestures
keep the full pasteboard, including image and file upload.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 2 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 4d8184a7-ad43-4623-a8fb-5b4d6d798f65

📥 Commits

Reviewing files that changed from the base of the PR and between ce5cb45 and 2a2e771.

📒 Files selected for processing (9)
  • Packages/macOS/CmuxTerminal/Tests/CmuxTerminalTests/Services/TerminalPasteboardServiceTests.swift
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/Clipboard/TerminalUnsafePasteConfirmationPolicy.swift
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/SurfaceCallbacks/RuntimeClipboardReadContent.swift
  • Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/RuntimeClipboardReadContentTests.swift
  • Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/TerminalUnsafePasteConfirmationPolicyTests.swift
  • Resources/Localizable.xcstrings
  • Sources/GhosttyApp+RuntimeClipboardRead.swift
  • Sources/GhosttySurfaceCallbackContext+ClipboardInputSequencing.swift
  • Sources/TerminalImageTransfer.swift

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

Conflicts:
- Sources/GhosttyApp+RuntimeClipboardRead.swift: main (#15113) made the
  file-URL branch resolve its transfer target asynchronously and revalidate
  the request after detection. Kept that, with this branch's plain-text
  guard ahead of it so a read the terminal program started never resolves
  a target, saves or uploads files.
- Resources/Localizable.xcstrings: key-level merge with
  scripts/merge-xcstrings.py; main's catalog plus this branch's three
  terminal.clipboardReadConfirmation keys.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

CI failure attribution

CI failed on 2a2e771391 (run 36484986186 attempt 4): 1 unknown.

Job Verdict Why
macos / CLI product tests unknown no known signature; failed step: Run shell and CLI no-socket regressions

Not re-run automatically: macos / CLI product tests is not a machine failure.

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's.

github-actions Bot added a commit that referenced this pull request Sep 28, 2026
github-actions Bot added a commit that referenced this pull request Sep 28, 2026
github-actions Bot added a commit that referenced this pull request Sep 28, 2026
github-actions Bot added a commit that referenced this pull request Sep 28, 2026
github-actions Bot added a commit that referenced this pull request Sep 28, 2026
@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Dogfood tours of 2a2e7713

modifier-clicks-tour at 2a2e7713: passed (run)

modifier-clicks-tour at 2a2e7713

Key frames of modifier-clicks-tour at 2a2e771 08-01-plain-hover-example 11-01-cmd-hover-example 14-01-cmd-click-opens-example 21-02-cmd-click-opens-github

Tours are picked by the paths globs in dogfood/scenarios/*.json; a Dogfood-tours: a, b line in the description picks them instead (none turns this off). Look at every frame before merging: a green tour only means no step failed.

github-actions Bot added a commit that referenced this pull request Sep 28, 2026
github-actions Bot added a commit that referenced this pull request Sep 28, 2026
github-actions Bot added a commit that referenced this pull request Sep 28, 2026
github-actions Bot added a commit that referenced this pull request Sep 28, 2026
github-actions Bot added a commit that referenced this pull request Sep 28, 2026
github-actions Bot added a commit that referenced this pull request Sep 28, 2026
github-actions Bot added a commit that referenced this pull request Sep 28, 2026
austinywang added a commit that referenced this pull request Sep 28, 2026
…-path

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@austinywang

Copy link
Copy Markdown
Contributor Author

Closing as superseded by merged PR #15116, which consolidated this SSH/security/local-state hardening into main.

@austinywang austinywang closed this Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant