What Happens When You Paste into a Terminal: Bracketed Paste, the 4095-Byte Line, and UTF-8 Boundaries

Part 3 of "Building an SSH Terminal". Code excerpts are from MistTerm v1.2.26, an open-source (AGPL) SSH terminal client by MistLab; they are trimmed, and source comments are translated from Chinese.

TL;DR

Bracketed paste is an xterm mode (DEC private mode 2004). Once a program enables it, the terminal wraps each paste in ESC[200~ … ESC[201~, so pasted newlines are inserted as text instead of acting as Enter. Readline turns it on by default from version 8.1. Separately, Linux's n_tty caps a canonical-mode line at 4095 bytes plus the newline. Large pastes should be sent in chunks that never split a UTF-8 character.

To a terminal, pasting is typing very fast. The clipboard text is written to the PTY exactly as if the user had typed it. Nothing in the byte stream separates "typed" from "pasted". That causes three separate problems. The shell cannot tell a pasted newline from a pressed Enter. The kernel limits how long one line can be. And splitting a large paste into chunks can cut a multi-byte character in half.

Problem 1: a pasted newline is an Enter. How does bracketed paste work?

Paste three lines into bash without any protocol and bash runs each line as soon as its newline arrives. That is inconvenient for a multi-line snippet. It is dangerous for text copied from a web page, which can contain content you never saw rendered, a newline included.

The fix is bracketed paste, an xterm extension documented in ctlseqs. A program that wants it enables DEC private mode 2004 (CSI ? 2004 h). From then on, the terminal wraps each paste:

ESC [ 2 0 0 ~   <pasted text>   ESC [ 2 0 1 ~
Bracketed paste: the program asks for mode 2004, the terminal wraps the paste
Bracketed paste: the program asks for mode 2004, the terminal wraps the paste

A program that sees the start marker treats everything up to the end marker as literal text. Readline inserts it into the line buffer without executing it, zsh's line editor (since zsh 5.1) does the same, and Vim, documented under :help xterm-bracketed-paste, inserts it without auto-indent.

In GNU Readline, the library behind bash's prompt, bracketed paste became on by default in readline 8.1. The NEWS entry reads "Bracketed paste mode is enabled by default", and the source shows the switch:

/* readline-8.1, rlprivate.h */
#define BRACKETED_PASTE_DEFAULT 1
/* readline-8.1, readline.c */
int _rl_enable_bracketed_paste = BRACKETED_PASTE_DEFAULT;

In readline 8.0 the same variable is initialized to 0. Bash 5.1 ships readline 8.1, so on current distributions an interactive bash asks for mode 2004 unless the user has set enable-bracketed-paste off in .inputrc. Readline also leaves the mode off for terminals it considers unsuitable (see terminal.c), such as TERM=dumb.

The terminal's side of the contract is small but has to be exact. MistTerm checks the mode bit the remote program set (Part 1), normalizes line endings, and wraps the payload:

// src/ui/terminal_paste.rs
/// Normalize newlines to `\n`; when bracketed paste is on, wrap in
/// `ESC[200~ … ESC[201~` and strip forged markers from the body
/// (so pasted content cannot "end the paste" early and be run as commands).
pub fn build_paste_payload(text: &str, bracketed: bool) -> Vec<u8> {
    let normalized = text.replace("\r\n", "\n").replace('\r', "\n");
    if !bracketed {
        return normalized.into_bytes();
    }
    let body = normalized.replace("\x1b[200~", "").replace("\x1b[201~", "");
    let mut out = Vec::with_capacity(body.len() + BP_START.len() + BP_END.len());
    out.extend_from_slice(BP_START);
    out.extend_from_slice(body.as_bytes());
    out.extend_from_slice(BP_END);
    out
}

The marker stripping is the security-relevant line. If clipboard text contains a literal ESC[201~, a naive terminal forwards it, the program believes the paste ended, and whatever follows is interpreted as typed input, newline and all. The unit test pins this behavior:

let p = build_paste_payload("ls\x1b[201~rm -rf x\n", true);
assert_eq!(p, b"\x1b[200~lsrm -rf x\n\x1b[201~");

Bracketed paste only helps if the receiving program enabled it. In a program that did not (a plain cat, a script reading with read, a REPL without support), a pasted newline is still an Enter. That is a property of the program, not something a terminal can change.

Problem 2: why are long pasted lines cut at 4095 bytes?

Programs that leave the TTY in canonical mode (POSIX termios ICANON) do not edit lines themselves. cat, the shell builtin read and Python's input() when stdin is not readline-enabled all fall in this group. The kernel's line discipline buffers input until a newline and implements Backspace, Ctrl-U and similar editing. That buffer is finite.

On Linux the buffer is defined in drivers/tty/n_tty.c:

#define N_TTY_BUF_SIZE 4096

n_tty_receive_buf_common handles the full-buffer case. In canonical mode, when the buffer is full and contains no complete line, the kernel keeps one slot free by re-using the last position. That lets a terminating newline still be accepted, while further characters are discarded. The effective result is that one canonical-mode line holds at most 4095 bytes plus its newline. Anything beyond is silently lost, and the reading program never knows.

Canonical-mode line buffer on Linux
Canonical-mode line buffer on Linux

Two details are easy to get wrong:

A terminal cannot work around this. The limit is in the server's kernel, below sshd. What a terminal can do is warn. SSH gives the client no way to query the remote termios state, so MistTerm uses the best signal it has. If the remote program has not enabled bracketed paste and some line in the clipboard is longer than 4095 bytes, it shows a warning before sending:

let bracketed = self.terminal.is_bracketed_paste();
// In line mode (cat/read, …) a single line > 4095 bytes is truncated by the
// tty layer; the terminal can't avoid that, so tell the user.
if !bracketed && exceeds_canon_line_limit(&normalized) {
    self.pending_toast_error = Some(/* suggest SFTP upload or pasting in vim */);
}

This is a heuristic. A program in raw mode without bracketed paste would trigger a spurious warning. The text is still sent unchanged either way. Interactive shells and editors that enable bracketed paste run the TTY in non-canonical mode and read the bytes themselves, so they are not subject to this limit.

Problem 3: chunking without breaking characters

A paste of several megabytes cannot go to the session thread as one message (Part 2). MistTerm queues it and writes it in pieces of at most PASTE_CHUNK = 4096 bytes. Cutting a byte buffer at an arbitrary offset can split a UTF-8 sequence: a CJK character is three bytes and an emoji four. Most receivers reassemble the bytes correctly, but treating each write as a unit of text is common enough that it is worth not producing broken fragments. UTF-8 makes the boundary easy to find, because continuation bytes always match 10xxxxxx:

Backing up to a UTF-8 character boundary
Backing up to a UTF-8 character boundary
/// Next chunk length: at most `max`, never splitting a UTF-8 multi-byte
/// character (unless a single character exceeds `max`).
pub fn next_chunk_len(buf: &[u8], max: usize) -> usize {
    if buf.len() <= max {
        return buf.len();
    }
    let mut n = max;
    // UTF-8 continuation bytes look like 10xxxxxx; back up to a character start.
    while n > 0 && (buf[n] & 0xC0) == 0x80 {
        n -= 1;
    }
    if n == 0 { max } else { n }
}

Note that the check is on buf[n], the first byte of the next chunk. If that byte is a continuation byte, the cut is inside a character, and the loop moves back to that character's lead byte.

The send loop is driven by the UI frame clock, not by a blocking loop. A chunk is removed from the pending buffer only after it was accepted by the session queue. If the queue is full, the same chunk is retried after 20 ms, up to 500 times (about ten seconds without progress). Otherwise the next chunk follows 5 ms later:

const GAP: Duration = Duration::from_millis(5);
const RETRY_GAP: Duration = Duration::from_millis(20);
const MAX_RETRIES: u32 = 500; // give up after ≈10 s without progress

If it finally gives up, the user is told how many bytes were not sent. A paste is never silently truncated by the terminal itself.

Summary

FAQ

How does bracketed paste work?

A program enables DEC private mode 2004 with CSI ? 2004 h. The terminal then wraps each paste in ESC[200~ and ESC[201~, and the program treats everything between them as literal text.

Is bracketed paste enabled by default in bash?

Readline enables it by default from 8.1, and bash 5.1 ships readline 8.1. Users can turn it off with enable-bracketed-paste off in .inputrc.

Why must a terminal strip ESC[201~ from pasted text?

If the clipboard contains the end marker, the rest of the paste would be treated as typed input, including its newlines.

Why are pasted lines longer than 4095 bytes cut off on Linux?

In canonical mode, Linux's n_tty line discipline holds at most 4095 bytes plus the newline. MAX_CANON (255) is not that limit. A terminal cannot work around it, but it can warn.

Related in this series

← Part 2 · Series index · Part 4 →