Skip to content

feat(settings): a live toggle for OSC 52 clipboard reads - #22

Open
kingb wants to merge 1 commit into
fix/osc52-read-engine-gatefrom
feat/clipboard-read-setting
Open

kingb wants to merge 1 commit into
fix/osc52-read-engine-gatefrom
feat/clipboard-read-setting

Conversation

@kingb

@kingb kingb commented Sep 28, 2026

Copy link
Copy Markdown
Owner

Adds "Programs can read clipboard" to Settings → Terminal: a toggle for the existing osc52_read config switch, and makes the switch live.

Stacked on #21. Without #21's engine-gate fix, no clipboard read ever reaches the listener, so this toggle would do nothing. This PR's base is #21's branch; once #21 merges, GitHub retargets it to main. Review it separately; merge it after #21.

What changes for a user

  • A new Terminal row, off by default. The help text says what it enables (nvim or tmux over SSH pasting from your local clipboard) and the risk (any program in the terminal can then read what you copied).
  • It takes effect immediately, including in open panes.

Why "live" matters here

Before, each pane copied osc52_read when it was spawned. For most settings, "new panes only" is fine. For a privacy switch it's the wrong failure: turning reads off would leave every open pane still sharing the clipboard. Now the app holds one shared gate (Arc<AtomicBool> in Shared), seeded from the loaded config, handed to every pane, and updated in adjust_setting alongside the other live side effects. Each read checks it at that moment.

Tests

Test Proves
clipboard_read_toggle_is_off_by_default_and_mutates_only_osc52_read the row defaults off, flips only osc52_read, and flips back
osc52_read_survives_a_save_and_load the setting round-trips through config.toml; an older file without the key loads with reads off
osc52_read_gate_is_live_in_a_running_pane off → on → off in one running pane, with no restart; "on" returns the clipboard (base64), "off" returns empty
the existing listener tests now panic if anything reads the clipboard while reads are off

Sabotage-checked: making the listener ignore the gate fails the "on" read. Making it always read trips the "must not be read" panic and fails the "off" read.

Not covered by an automated test: the two lines of app wiring (seeding Shared, updating in adjust_setting). Covering them needs a full WindowState. They're small and visible in the diff; worth a look in review.

Validation

cargo fmt --check, clippy -D warnings and cargo test --all --all-features are clean. CHANGELOG [Unreleased] has an Added entry, above #21's Fixed.

🤖 Generated with Claude Code

https://claude.ai/code/session_014xVjnf8UuQPRQtv2QqgpPK

osc52_read was a config.toml-only switch. It is now also a Settings row,
"Programs can read clipboard", in the Terminal section: off by default, with
help text that names the use (nvim or tmux over SSH pasting from the local
clipboard) and the risk (any program in the terminal can read what you
copied).

The switch is also live now. Before, each pane copied the value when it was
spawned, so turning reads OFF left every open pane still sharing the
clipboard, which is the wrong failure for a privacy switch. The app now holds
one shared gate, hands every pane a clone, and updates it on every settings
change; each read checks it at that moment.

- ember-session: LocalPtyConfig::osc52_read is a shared Arc<AtomicBool>, and
  the listener reads the clipboard through an injectable source so tests can
  prove what is and isn't read.
- ember-core: the Settings row and its toggle, plus the config doc.
- ember-app: the gate lives in Shared, seeded from the loaded config and
  updated in adjust_setting alongside the other live side effects.

Tests: the row is off by default and flips only osc52_read; the setting
survives a config.toml save and load (and an older file without the key
loads with reads off); and the gate is live in a running pane, off -> on ->
off, with the clipboard never read while off. The existing listener tests now
fail if anything reads the clipboard while reads are off. Sabotage-checked:
ignoring the gate, or always reading, each fails.

Builds on the OSC 52 engine-gate fix, without which no read reaches the
listener.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014xVjnf8UuQPRQtv2QqgpPK
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