osc copy (v0.4.8 and main) trusts the SSH_TTY environment variable without checking that it matches the caller's controlling terminal. When SSH_TTY is stale, osc writes the OSC 52 sequence to a different pty than the user's terminal. On a shared/multi-user host that pty often belongs to a different logged-in user, who silently receives the clipboard payload in their terminal emulator (and the original user's clipboard is never updated).
How I hit this
- macOS client with
~/.ssh/config:
Host *
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
- Multi-user Linux dev box (NixOS, ~4 active SSH users).
- Inside an SSH session:
tty reports /dev/pts/1543, but echo $SSH_TTY reports /dev/pts/0 — the master/first session's tty, which now belongs to a different user.
echo hello | osc copy silently sends \x1b]52;c;<base64>\x1b\\ to /dev/pts/0 (the other user's terminal). My local clipboard is never updated.
The same shape of bug applies to mosh-resumed sessions (already noted in #7) and tmux re-attach (#17).
Reproduction
https://github.com/schickling-repros/2026-04-osc-stale-ssh-tty
git clone https://github.com/schickling-repros/2026-04-osc-stale-ssh-tty
cd 2026-04-osc-stale-ssh-tty
./repro.sh
The script forces a stale SSH_TTY (a real but unrelated pts on the same host) and shows that osc copy -v writes OSC 52 to that unrelated pts rather than to the caller's controlling terminal.
Sample output on a host with /dev/pts/1543 as the real tty and /dev/pts/0 available:
Caller's controlling tty: /dev/pts/1543
Stale SSH_TTY (simulated): /dev/pts/0
...
DEBUG Using tty device: /dev/pts/0
DEBUG tty write: 45 <nil> "\x1b]52;c;b3NjLXNzaC10dHktYnVnLTE3NzcxOTIxNDAK\x1b\\"
osc wrote OSC 52 to: /dev/pts/0
Source
main.go:524-534 (v0.4.8):
func ttyDevice() string {
if deviceFlag != "" {
return deviceFlag
} else if isScreen {
return "/dev/tty"
} else if sshtty := os.Getenv("SSH_TTY"); sshtty != "" {
return sshtty // trusted blindly
} else {
return "/dev/tty"
}
}
Suggested fix
Either:
- Drop the
SSH_TTY branch entirely. /dev/tty (the controlling terminal) is correct in all the cases I can construct, including plain SSH, tmux, and screen.
- Or
stat() SSH_TTY and only use it when it is owned by the caller and matches the controlling terminal. Otherwise fall back to /dev/tty.
Option (1) is the simpler change and would also subsume #17. Happy to send a PR if you'd like.
Workarounds for users
osc copy -d /dev/tty
SSH_TTY= osc copy
Versions
osc: 0.4.8 (also reproduces on main as of 2026-04-26)
- OS: NixOS, Linux 6.18, multi-user
- Client: macOS 25.2, OpenSSH with
ControlMaster auto
Related
Filed by an AI assistant on behalf of @schickling
Posted on behalf of @schickling
| field |
value |
agent_name |
🐤 cl1-finch |
agent_session_id |
fb1c07ee-69fb-4b7d-a948-95f8a524e8c5 |
agent_tool |
Claude Code |
agent_tool_version |
2.1.118 (Claude Code) |
agent_runtime |
Claude Code 2.1.118 (Claude Code) |
agent_model |
claude-opus-4-7 |
worktree |
dotfiles/schickling/2026-04-26-misc |
machine |
mbp2025 |
tooling_profile |
dotfiles@cda3c8e |
osc copy(v0.4.8 andmain) trusts theSSH_TTYenvironment variable without checking that it matches the caller's controlling terminal. WhenSSH_TTYis stale,oscwrites the OSC 52 sequence to a different pty than the user's terminal. On a shared/multi-user host that pty often belongs to a different logged-in user, who silently receives the clipboard payload in their terminal emulator (and the original user's clipboard is never updated).How I hit this
~/.ssh/config:ttyreports/dev/pts/1543, butecho $SSH_TTYreports/dev/pts/0— the master/first session's tty, which now belongs to a different user.echo hello | osc copysilently sends\x1b]52;c;<base64>\x1b\\to/dev/pts/0(the other user's terminal). My local clipboard is never updated.The same shape of bug applies to mosh-resumed sessions (already noted in #7) and tmux re-attach (#17).
Reproduction
https://github.com/schickling-repros/2026-04-osc-stale-ssh-tty
git clone https://github.com/schickling-repros/2026-04-osc-stale-ssh-tty cd 2026-04-osc-stale-ssh-tty ./repro.shThe script forces a stale
SSH_TTY(a real but unrelated pts on the same host) and shows thatosc copy -vwrites OSC 52 to that unrelated pts rather than to the caller's controlling terminal.Sample output on a host with
/dev/pts/1543as the real tty and/dev/pts/0available:Source
main.go:524-534(v0.4.8):Suggested fix
Either:
SSH_TTYbranch entirely./dev/tty(the controlling terminal) is correct in all the cases I can construct, including plain SSH, tmux, and screen.stat()SSH_TTYand only use it when it is owned by the caller and matches the controlling terminal. Otherwise fall back to/dev/tty.Option (1) is the simpler change and would also subsume #17. Happy to send a PR if you'd like.
Workarounds for users
osc copy -d /dev/ttySSH_TTY= osc copyVersions
osc: 0.4.8 (also reproduces onmainas of 2026-04-26)ControlMaster autoRelated
SSH_TTY.SSH_TTYbranch for the non-tmux case, so this bug would persist after that PR merges.Filed by an AI assistant on behalf of @schickling
Posted on behalf of @schickling
agent_nameagent_session_idagent_toolagent_tool_versionagent_runtimeagent_modelworktreemachinetooling_profile