Skip to content

fix(pty): strip device-query requests and drain leaked stdin responses - #435

Merged
jexShain merged 1 commit into
AI-Shell-Team:mainfrom
jexShain:fix/terminal-query-input-pollution
Aug 7, 2026
Merged

fix(pty): strip device-query requests and drain leaked stdin responses#435
jexShain merged 1 commit into
AI-Shell-Team:mainfrom
jexShain:fix/terminal-query-input-pollution

Conversation

@jexShain

@jexShain jexShain commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

Change Type

  • Bug fix

Scope

  • Core shell / PTY

User-visible Changes

修复远程 ssh/telnet 会话、tmux/screen、man/less 等场景下输入行被终端响应乱码污染的问题。用户不再需要在 prompt 里手动清除 0;115;0c 之类的残留片段。

Compatibility

  • Backward compatible? Yes
  • Config changes? No

Testing

  • cargo test -p aish-pty strip_device_queries → 7/7
  • cargo test -p aish-shell report_prefix → 8/8
  • cargo clippy -p aish-pty --all-targets -- -D warnings → 零警告
  • cargo clippy -p aish-shell --all-targets -- -D warnings → 零警告
  • PTY + termios 实验:验证 canonical(ICANON)模式下 poll() 对无换行的 report 返回不可读,raw 模式下可读——确认输入端必须在 poll 前进 raw 模式

Checklist

  • Code follows project style
  • Tests added if needed
  • Documentation updated if needed

实现细节

1. 输出端aish-pty write_stdout_all):剥离转发到真实终端的设备查询 请求(CPR/DA1/DA2/DA3/DSR),从源头阻止真实终端被诱导响应。真正的 TUI/远程程序(vim/less/ssh…)走 passthrough 透传,标志由 bool 改为引用计数 AtomicUsize,修正嵌套/并发命令下内层 guard 提前关闭的隐患。

2. 输入端aish-shell drain_terminal_responses):每次 readline 前(含 multiline 续行 > )进 raw 模式 + O_NONBLOCKpoll stdin。raw 模式必须在 poll 前——canonical 模式只按行释放输入,而 report 无行分隔符,poll 否则永远看不到。残留响应则丢弃前导完整 report 序列,剩余用户 type-ahead 用 TIOCSTI 重新注入;TIOCSTI 被内核禁用且非 report 字节丢失时记 warntail_len/reinjected)。

strip_device_queries 区分 请求响应:请求 private marker 只认 >/=(不认 ?),避开误删 DA1 响应 ESC[?64;1c;响应参数带 ; 自然落出 digit 扫描而被保留。

Fixes #434

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Thanks for the pull request. A maintainer will review it when available.

Please keep the PR focused, explain the why in the description, and make sure local checks pass before requesting review.

Contribution guide: https://github.com/AI-Shell-Team/aish/blob/main/CONTRIBUTING.md

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Template check passed. Thanks for updating the pull request description.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change filters terminal query requests from normal PTY output and drains leaked terminal responses before readline. Interactive and session commands retain raw output through nested guards. The shell preserves remaining type-ahead when it removes terminal responses.

Changes

Terminal response handling

Layer / File(s) Summary
PTY output filtering
crates/aish-pty/src/persistent.rs
Normal output strips CPR, DSR, and DA query requests. Interactive and session commands enable nested raw-output passthrough. Tests cover filtering and preservation cases.
Terminal response draining
crates/aish-shell/src/keyboard/raw_mode.rs, crates/aish-shell/src/keyboard/mod.rs
The shell drains leading terminal reports from stdin, reinjects remaining type-ahead through TIOCSTI, restores terminal settings, and provides Unix and non-Unix APIs.
Readline integration
crates/aish-shell/src/readline.rs
Readline entry points drain leaked CPR and DA responses before invoking rustyline, including multiline continuation reads.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PTY
  participant Terminal
  participant Readline
  participant stdin
  PTY->>Terminal: filter normal query requests
  PTY->>Terminal: pass through interactive/session output
  Terminal->>stdin: return terminal reports
  Readline->>stdin: drain leading reports before reading
  Readline->>Readline: invoke rustyline with preserved type-ahead
Loading

Possibly related PRs

Poem

A rabbit filters queries at night,
And drains stray reports from sight.
Type-ahead hops safely back in line,
While rustyline reads input fine.
Interactive output still runs free—
Hop, hop, clean terminal harmony.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes implement both protections requested by issue #434, including query stripping, response draining, type-ahead preservation, and passthrough tracking.
Out of Scope Changes check ✅ Passed All changes support issue #434 by implementing, integrating, testing, or platform-supporting the terminal query and response handling.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary changes: stripping PTY device queries and draining leaked stdin responses.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/aish-pty/src/persistent.rs`:
- Around line 100-124: Make device-query filtering stream-aware in both sites:
update strip_device_queries in crates/aish-pty/src/persistent.rs (lines 100-124)
to preserve incomplete CSI query candidates across PTY reads, resolving them
only when subsequent bytes confirm or reject the sequence while writing output
in order; update the raw-mode input handling in
crates/aish-shell/src/keyboard/raw_mode.rs (lines 104-108) to retain incomplete
report candidates across stdin reads and prevent their prefixes from reaching
rustyline until resolved.

In `@crates/aish-shell/src/keyboard/raw_mode.rs`:
- Around line 179-203: The report_prefix_len parser currently treats query
request sequences ending in n or t as discardable responses. Restrict
recognition to valid terminal response parameter shapes, excluding requests such
as ESC[5n, ESC[6n, and ESC[18t, while preserving existing response handling; add
tests asserting each request returns a prefix length of zero.
- Around line 91-104: Update the input-draining flow around
InputRawGuard::enter, set_stdin_nonblocking, stdin_readable_now, and
read_pending so raw mode and nonblocking I/O are established before the
readiness probe. Preserve the zero-timeout poll before any read, and return
early when no input is readable or raw-mode setup fails.

In `@crates/aish-shell/src/readline.rs`:
- Around line 747-750: Ensure crate::keyboard::drain_terminal_responses() runs
immediately before every readline call, including the continuation readline path
in the multiline input flow. Preserve the existing initial-prompt cleanup and
add the same cleanup to the continuation path so pending terminal responses
cannot reach rustyline.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1764a7e2-2f0c-4fb7-883d-3878935a128a

📥 Commits

Reviewing files that changed from the base of the PR and between 06f2347 and cf2abd1.

📒 Files selected for processing (4)
  • crates/aish-pty/src/persistent.rs
  • crates/aish-shell/src/keyboard/mod.rs
  • crates/aish-shell/src/keyboard/raw_mode.rs
  • crates/aish-shell/src/readline.rs

Comment thread crates/aish-pty/src/persistent.rs
Comment thread crates/aish-shell/src/keyboard/raw_mode.rs Outdated
Comment thread crates/aish-shell/src/keyboard/raw_mode.rs
Comment thread crates/aish-shell/src/readline.rs
@jexShain
jexShain force-pushed the fix/terminal-query-input-pollution branch from cf2abd1 to 5d1baa7 Compare August 7, 2026 06:30
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

Terminal device-query responses (CPR/DA/DSR) leaked into stdin and were
read by rustyline as key events, corrupting the input line with garbled
fragments like `0;115;0cRRR`.

Two-layer defense:

1. Output side (aish-pty write_stdout_all): strip forwarded device-query
   *request* sequences (CPR/DA1/DA2/DA3/DSR) so the real terminal is never
   asked to respond. Real TUI/remote programs (vim/less/ssh/...) go through
   a passthrough flag, now a reference-counted AtomicUsize so nested/
   concurrent commands keep the outer guard armed until all inner guards
   drop.

2. Input side (aish-shell drain_terminal_responses): before each readline
   (including the multiline continuation path), enter raw mode + O_NONBLOCK,
   then non-blocking poll stdin. Raw mode is entered *before* the poll
   because canonical (cooked) mode only releases complete lines and a
   terminal report (ESC[2;2R) has no line delimiter — poll would otherwise
   never see it. Pending reports are read, the leading run of complete
   report sequences is dropped, and genuine type-ahead is reinjected via
   TIOCSTI. When TIOCSTI is rejected by the kernel and non-report bytes are
   lost, a warn (tail_len/reinjected) is logged for diagnosis.

strip_device_queries distinguishes requests from responses: the request
scan treats only '>'/'=' as private markers (not '?'), so DA1 responses
`ESC[?64;1c` pass through; responses carry ';' which falls outside the
digit scan and are preserved.

Tests: strip_device_queries x7, report_prefix_len x8; clippy
--all-targets -D warnings clean.

Fixes AI-Shell-Team#434
@jexShain
jexShain force-pushed the fix/terminal-query-input-pollution branch from 5d1baa7 to 62e88ed Compare August 7, 2026 06:36
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@jexShain
jexShain merged commit 05ae98e into AI-Shell-Team:main Aug 7, 2026
9 checks passed
@jexShain
jexShain deleted the fix/terminal-query-input-pollution branch August 7, 2026 06:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: 终端设备查询响应泄漏进 stdin,破坏输入行(乱码注入)

1 participant