[Fix]: Sanitize dangerous ANSI/OSC escape sequences in write_to_pty#174
[Fix]: Sanitize dangerous ANSI/OSC escape sequences in write_to_pty#174matiaspalmac wants to merge 2 commits intoTrixtyAI:mainfrom
Conversation
`write_to_pty` forwarded xterm.js `onData` output straight to the shell, including any OSC/DCS/APC/PM/SOS sequences that happened to be in a paste. These sequences exist for shell-to-UI signalling and have no legitimate reason to flow in the other direction: - OSC 52 can silently copy attacker-controlled data to the clipboard. - OSC 0/1/2 can retitle the window to impersonate another app. - OSC 7 can point the host at a bogus working directory. - DCS / APC / PM / SOS carry arbitrary payloads that a tolerant terminal may interpret. The fix adds a pure `sanitize_pty_input` helper that walks the input by codepoint (not by byte — UTF-8 continuation bytes for e.g. `😀` contain 0x98, which would otherwise collide with C1-SOS) and drops ESC]/ESCP/ESCX/ESC^/ESC_ sequences plus their 7-bit C1 codepoint equivalents through the next String Terminator (`ST` = ESC\ or 0x9C; BEL also closes an OSC). CSI (`ESC[`) is preserved because xterm.js relies on it for arrow keys, bracketed paste, mouse events, and normal terminal input. Unterminated sequences are dropped through end-of-input so a payload split across two `write_to_pty` calls cannot sneak through. Fixes TrixtyAI#61
|
Thanks for the contribution! I'll review it as soon as possible. If you have still changes, please mark this PR as draft and all reviews will be cancelled. Tests reviews will be re-run only when the PR is marked as ready for review. |
There was a problem hiding this comment.
Pull request overview
This PR mitigates terminal-input injection by sanitizing xterm.js onData input before it is written to the backend PTY, preventing OSC/DCS/APC/PM/SOS payloads (e.g., OSC 52 clipboard writes) from reaching the shell.
Changes:
- Added
sanitize_pty_input(&str) -> Stringto strip OSC/DCS/APC/PM/SOS (including C1 control-code equivalents) until ST/BEL as appropriate. - Updated
write_to_ptyto write sanitized input to the PTY. - Added unit tests covering pass-through behavior (plain text/CSI/bare ESC) and stripping behavior (OSC/DCS/APC/PM/SOS, unterminated sequences, back-to-back sequences).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Copilot flagged the phrase '7-bit C1 byte' as misleading — the listed values (U+0090/U+0098/U+009D/U+009E/U+009F) are C1 control code points, typically described as 8-bit (or, in a Rust `&str`, as multi-byte UTF-8 code points). Reworded the doc and the inline comment to say 'C1 control code points' and to make the UTF-8 encoding explicit (e.g. `U+009D` encodes as `0xC2 0x9D`). No behavior change; tests still pass 10/10.
|
Addressed Copilot review: reworded the doc and inline comment on |
Description
write_to_ptyforwarded xterm.jsonDataoutput straight to the shell, so any OSC/DCS/APC/PM/SOS sequence that arrived via a paste or programmatic input (an AI reply, a README snippet, a log file) reached the TTY unchanged. These sequences are meant for shell-to-UI signalling and have no legitimate reason to flow in the other direction.Concrete risks this closes:
Related Issue
Fixes #61
Context / Problem
xterm.js's
onDatacallback is the sole input path for user-typed keys and pasted content. Pasted text is not distinguished from typed text at that layer, so an OSC 52 payload embedded in e.g. an AI-assistant response pasted into the terminal went straight through to the shell. The terminal emulator on the read path then honored the clipboard-set request.Change
sanitize_pty_input(&str) -> String— strips:ESC ](OSC),ESC P(DCS),ESC X(SOS),ESC ^(PM),ESC _(APC)U+0090,U+0098,U+009D,U+009E,U+009F)ST=ESC \orU+009C;BEL/U+0007also closes an OSC).😀contains 0x98) would otherwise be mistaken for a C1 introducer.ESC [) is preserved because xterm.js emits it for arrow keys, bracketed paste (CSI 200 ~/CSI 201 ~), mouse events, and other normal terminal input.write_to_ptycalls.write_to_ptypipes input through the sanitizer before writing to the shell.Trade-offs
Verification
cargo test --lib→ 37 passed.cargo clippy --lib --no-deps -- -D warnings→ clean.Checklist