How can I protect myself from this kind of clipboard abuse?

You might have guessed this, but never use the terminals pasting functionality to paste things into vim/emacs. It's like sending a batch of commands to the editor, that can do anything.

For these reasons, editors have their own copy-pasting functionality, which cannot be injected. For instance, in vim, you should use the + register to exchange data with the system clipboard ("+p for pasting).

Regarding the shell or other terminal applications: It has been established, that you must not paste unsafe data into your terminal.

There is a safe-paste plugin for zsh, which prevents code from actually running when pasted, but someone has already exploited it anyways.

Also, a similiar question (about accidental pasting) has been asked on apple.se. Most of the solutions might also work for you.

Update: In vim, if set mouse=a is used, pasting with the middle mouse button is safe. You can still paste with shift-Insert though.


Note that as of version 292, xterm removes ASCII control characters except \b, \r, \t, DEL (0x7f) and \n (it converts \n to \rs like other terminals), and you can bring them back with the allowPasteControls resource. VTE (the terminal emulator library used by gnome-terminal, terminator, xfce-terminal...) also does it since October 2015

So in those terminals, ^C, ^[, ^D, ^Z, ^\, ^U, ^W are no longer a problem but DEL, \b, \t (very dangerous with some configurations (including the default one) of zsh where completion can expand command substitutions), \r and \n still are.

xterm also has a couple of paste modes that can help here.

  • the bracketed paste mode enabled with the \e[?2004h sequence as used in some zsh or vim safe-paste plugins.

    zsh (zle) since 5.1 and bash (readline) since 4.4 now have support for that built in. While it's enabled by default in zsh, you need to enable it manually with bind 'set enable-bracketed-paste on' in bash (or in the readline configuration in ~/.inputrc or /etc/inputrc).

    This one wraps the selection between \e[200~ and \e[201~.

    Most other modern terminals like VTE-based ones (gnome-terminal, xfce-terminal, terminator...), rxvt, konsole, OS/X Terminal now also support that one.

    In some of those other terminals (or versions thereof) though (or with allowPasteControls in xterm), that's flawed in that \e[201~ may appear in the selection, and would be taken as the closing bracket.

    That could be fixed by bracketing like \e\e[201~\e[200~201~, but it's not done by any terminal emulator yet AFAIK (and would mean the application would see several pastes).

    ^C/^Z/^\ would also still cause signals to be sent to the foreground process group of the terminal if ISIG was not disabled in the tty line discipline.

  • The quoted paste mode enabled with the \e[?2005h sequence (disabled with \e[?2005l).

    This one prepends every character (actually byte) with a ^V character.

    ^V is the default lnext (literal next) character of the tty line discipline in canonical mode, and is also recognised as such by vi and other editors and some line editors like readline or zsh's zle.

    That one doesn't have the same problem as the bracketed mode above, and has the benefit to work for the terminal canonical mode (like when you do cat > file) and a few other applications but has a few drawbacks:

    • newline and CR end up being rendered as ^M. That can be avoided with another escape sequence: \e[?2006h, but that causes the newlines to be inserted as NUL characters in vim and show up as ^J (unless you do stty -echoctl) in the terminal canonical mode (though it's only a cosmetic issue).
    • That doesn't work great for multi-byte characters which are not inserted properly in zle or vim for instance.
    • some visual applications don't handle ^V as literal next, so you may still have to turn it off selectively.
    • you can't use it in vim as ^V 1 for instance doesn't insert 1 but ^A there.
    • I'm not aware of any other terminal beside xterm supporting it, but then I've not done an extensive survey.
  • it also lets you define your own bracketed paste mode via configuration. For instance, with:

     XTerm*allowPasteControls: true
     XTerm.VT100.translations: #override \
       Ctrl Shift<KeyPress> Insert: \
         insert-formatted("\033[202~%S~%s", CLIPBOARD,PRIMARY,CUT_BUFFER0)'
    

    it would insert the CLIPBOARD/PRIMARY/CUT_BUFFER0 as ^[[202~<size-in-bytes>~<content> upon Shift+Ctrl+Insert. The application could then interpret that reliably (it would still need to disable ISIG in the tty line discipline though).

Another approach would be to use a pseudo-tty wrapper that inserts those ^V only in front of control characters. Such wrapper should be able to detect control characters in pastes with some reliability because real keyboard keypresses would only send one character at a time or a sequence of characters starting with ESC, while pastes would send several at a time.

You'd still have the problem of newlines shown as ^J in the terminal canonical mode or ^@ in vim, but that could be worked around with with some cooperation by the shell

A proof of concept:

To be used for instance as:

./safe-paste bash

To start a bash shell under that wrapper.

#!/usr/bin/perl

use IO::Pty;
use IO::Stty;

my $pty = new IO::Pty;
my $pid = fork();
die "Cannot fork" if not defined $pid;
unless ($pid) {
  $pty->make_slave_controlling_terminal();
  my $slave = $pty->slave();
  close $pty;
  $slave->clone_winsize_from(\*STDIN);

  open(STDIN,"<&". $slave->fileno())
    or die "Couldn't reopen STDIN for reading, $!\n";
  open(STDOUT,">&". $slave->fileno())
    or die "Couldn't reopen STDOUT for writing, $!\n";
  open(STDERR,">&". $slave->fileno())
    or die "Couldn't reopen STDERR for writing, $!\n";

  close $slave;

  exec(@ARGV);
  die "Cannot exec(@ARGV): $!";
}
$pty->close_slave();

$SIG{WINCH} = sub {
  $pty->slave->clone_winsize_from(\*STDIN);
};

my $old = IO::Stty::stty(\*STDIN, '-g');
IO::Stty::stty(\*STDIN, 'raw', '-echo');
$tty = fileno($pty);
my ($rin,$ein) = ('','','');
vec($rin, 0, 1) = 1;
vec($rin, $tty, 1) = 1;
vec($ein, $tty, 1) = 1;
my ($to_stdout, $to_tty) = ('', '');
my $eof;
$SIG{CHLD} = sub {$eof = 1};
until ($eof && $to_stdout eq '' && $to_tty eq '') {
  my ($rout,$wout,$eout,$timeleft);
  my $win = '';
  vec($win, 0, 1) = 1 if ($to_stdout ne "");
  vec($win, $tty, 1) = 1 if ($to_tty ne "");
  ($nfound,$timeleft) = select($rout=$rin,$wout=$win,$eout=$ein,undef);
  if ($nfound > 0) {
    if (vec($eout, $tty, 1)) {
      print STDERR "Exception on $tty\n";
    }
    if (vec($rout, 0, 1)) {
      my $buf;
      if (sysread(STDIN, $buf, 4096)) {
        if ($buf =~ /.[\0-\037\177]/ || $buf =~ /^(?:[\0-\032\034-\037]|\033.*?[~a-zA-NP-Z])./) {
          $buf =~ s/[\0-\037\177]/\026$&/g;
          # TODO: add UTF-8 sanitizing
          $buf =~ y/\r/\n/;
        }
        $to_tty .= $buf;
      } else {
        $eof = 1;
        vec($rin, 0, 1) = 0;
      }
    }
    if (vec($rout, $tty, 1)) {
      my $buf;
      if (sysread($pty, $buf, 4096)) {
        $to_stdout .= $buf;
      } else {
        $eof = 1;
        vec($rin, $tty, 1) = 0;
        $to_tty = '';
      }
    }
    if ($to_tty ne '' && vec($wout, $tty, 1)) {
      my $written = syswrite($pty, $to_tty);
      $to_tty = substr($to_tty, $written) if $written;
    }
    if ($to_stdout ne '' && vec(wout, 1, 1)) {
      my $written = syswrite(STDOUT, $to_stdout);
      $to_stdout = substr($to_stdout, $written) if $written;
    }
  }
}
END{IO::Stty::stty(\*STDIN, $old)}

A better approach would probably be to use a clipboard manager where you can specify the paste mode and that would flag potentially dangerous selections.


Well, it turns out that my current approach to clipboarding is good at mitigating this.

When copy pasting snippets between tabs, I just copy paste normally.

However, when copy pasting into a terminal/PuTTY session, I (being a bit averse to editing the text in the terminal), usually assemble it in Notepad++ or Emacs (depending on OS) and then copy-paste the final text into the terminal. Both editors show control characters (and other non-printable characters), so it's easy to notice any skullduggery there.

I unfortunately can't claim that I use the intermediate-text editor approach for security reasons, it's because I'm not yet adept at vim or any other terminal-based editor.