Skip to content

fix: replace unsafe control characters in pasted text (CVE-2026-26982) - #193

Open
livenson wants to merge 2 commits into
coder:mainfrom
waldur:fix/paste-bracket-fence
Open

livenson wants to merge 2 commits into
coder:mainfrom
waldur:fix/paste-bracket-fence

Conversation

@livenson

@livenson livenson commented Sep 13, 2026

Copy link
Copy Markdown

Summary

Pasted control characters can run commands in bash and zsh (CVE-2026-26982). Native Ghostty fixed this in v1.3.0 (ghostty-org/ghostty#10746), following xterm. ghostty-web encodes pastes in TypeScript rather than through Ghostty's src/input/paste.zig, so it is still affected, and a newer Ghostty core alone would not fix it.

A second, related problem: bracketed paste (DEC mode 2004) wraps pasted text in ESC[200~ESC[201~, and the text was wrapped as-is. Clipboard content containing ESC[201~ closed the bracket early, and everything after it reached the application as if typed. For example, pasting ls\x1b[201~id\r into a shell with bracketed paste enabled ran id.

Change

  • New lib/paste.ts with encodePaste(text, bracketed), a port of Ghostty 1.3.0's encode. In either paste mode, it replaces these bytes with a space, the same list Ghostty copied from xterm:
    • NUL, BS, ENQ, EOT, ESC and DEL;
    • the line-discipline characters: Ctrl+C, Ctrl+, Ctrl+U, Ctrl+Z, Ctrl+Q, Ctrl+S, Ctrl+W, Ctrl+V, Ctrl+R and Ctrl+O. The pty acts on these even inside a bracketed paste.
  • Because ESC is replaced, pasted text can no longer close the bracketed-paste fence. ls\x1b[201~id\r now arrives as \x1b[200~ls [201~id\r\x1b[201~.
  • Tabs, newlines and carriage returns are kept, as in Ghostty.
  • Both paste paths use the encoder: InputHandler (the browser paste event and insertFromPaste) and Terminal.paste().
  • Not ported: Ghostty also converts \n to \r outside bracketed paste. That is not a security measure, so this PR leaves it as it was.

Testing

  • lib/paste.test.ts covers the encoder, including the cases from Ghostty's paste.zig tests. There are also bracketed-paste cases in input-handler.test.ts and terminal.test.ts.
  • bun run fmt && bun run lint && bun run typecheck && bun test && bun run build all pass, with tests run against a WASM built from this tree (Zig 0.15.2).
  • In headless Chromium, a real ClipboardEvent was checked against a build containing this change.

#169 also reworks the bracketed-paste code in terminal.ts; happy to rebase onto whichever lands first.

Bracketed paste (DEC mode 2004) fences pasted text between ESC[200~ and
ESC[201~ so the application treats it as data. Pasted text was wrapped
as-is, so clipboard content containing ESC[201~ closed the fence early
and everything after it reached the application as typed input: the
paste injection attack.

Remove every ESC[201~ from the text before fencing it, repeating until
none is left so a marker split around another cannot be reassembled.
Native Ghostty never trusts such a paste either (isSafe in
src/input/paste.zig). Both paste paths, the browser paste event and
Terminal.paste(), now share one encoder. Unbracketed pastes are
unchanged.
Pasted control characters can run commands in bash and zsh
(CVE-2026-26982). Native Ghostty fixed this in v1.3.0
(ghostty-org/ghostty#10746) the way xterm does: NUL, BS, ENQ, EOT, ESC,
DEL and the line-discipline characters (Ctrl+C, Ctrl+\, Ctrl+U, Ctrl+Z,
Ctrl+Q, Ctrl+S, Ctrl+W, Ctrl+V, Ctrl+R, Ctrl+O) are replaced with spaces
in either paste mode, since the pty line discipline acts on them even
inside a bracketed paste. ghostty-web encodes pastes in TypeScript, so a
newer Ghostty core would not have fixed it here.

Port the same rule and Ghostty's test cases. Replacing ESC also means
pasted text can no longer close the bracketed-paste fence with ESC[201~,
which supersedes stripping that marker. Tabs, newlines and carriage
returns are kept, as in Ghostty.
@livenson livenson changed the title fix: strip end-of-paste markers from bracketed pastes fix: replace unsafe control characters in pasted text (CVE-2026-26982) Sep 13, 2026
livenson added a commit to waldur/ghostty-web that referenced this pull request Sep 13, 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