Skip to content

test(tui): hostile-output safety and layout matrix for Next, fixing six leaks (#417 slice) - #438

Open
BunsDev wants to merge 4 commits into
feat/415-memory-inspectorfrom
test/417-safety-matrix
Open

BunsDev wants to merge 4 commits into
feat/415-memory-inspectorfrom
test/417-safety-matrix

Conversation

@BunsDev

@BunsDev BunsDev commented Nov 2, 2026

Copy link
Copy Markdown
Member

Context

What the tests found and what changed (Next only)

Leak Path Fix
Notification toast drew its message raw, in hard-coded truecolor even under NO_COLOR Next reused Classic's banner A Next-native one-row toast in the selected color mode, with inert text, placed in the transcript pane. It never covers the header, an approval or the inspector. Classic keeps its banner.
Error notices drew provider error text raw and in color Next handed the whole frame to Classic's error modal Next draws its own inert, wrapped error panel over the composer. Esc still dismisses it through the shared key handling.
Header showed the session title, project, model and familiar raw workspace_line Every part goes through safe_one_line
Session page and approval details showed model, provider, familiar, project and title raw field() field() makes every value inert
Attachment labels showed media type, document title and pasted file name raw application/view.rs Made inert
Terminal title could carry a stored session title's control characters into the OSC title sequence update_terminal_title at 4 call sites In Next, terminal_title_topic drops control characters and bidi overrides. Classic is unchanged; see #437.

Classic is unchanged. notifications.rs and render.rs are not touched. The one changed parity test now states the new contract: Next draws error notices itself.

Tests

  • tui/tests/next_hostile_output.rs (9 tests). One corpus is placed in every untrusted field Next shows. It contains:

    • OSC 52 clipboard writes, OSC 8 hyperlinks and OSC 0 titles;
    • a screen clear and cursor homing;
    • C1 CSI and OSC, CR, BS, BEL and NUL;
    • a bidi override and isolate;
    • CJK, a combining mark and a ZWJ emoji.

    The suite runs these checks:

    • Control-character matrix: 3 variants (approval pending, plain, error notice) × 2 glyph sets × 4 color modes × 7 pages (no page plus all 6) × 6 sizes (20x6 to 180x50) = 1008 frames, with no C0, DEL or C1 in any cell.
    • Page coverage: every page draws a distinct frame, so a hidden inspector cannot pass for coverage.
    • NO_COLOR: no color on any page in any variant.
    • Way back: "Esc" stays visible on every page from 40x12 up.
    • Toast: it never covers the approval or the inspector title.
    • Error panel: Next draws it, and Esc dismisses it.
    • Title: the title topic carries no control characters.
  • presentation_parity.rs: the error modal is no longer in the list of surfaces handed to Classic. A new test covers the Next error panel and Esc.

  • tmux case 11: a fake claude answers with control sequences, and tmux's set-clipboard is on so a leak would be observable. The case checks that:

    • no paste buffer appears and the title carries no raw ESC or BEL;
    • a screen clear does not wipe earlier content;
    • the model's own OSC 52 and OSC 8 show as caret text.

    Honest note: at the PTY level, pasted prompt controls already arrive in the composer as caret text. A raw byte capture showed the base binary emits no raw OSC 52 for this journey either, so case 11 is a regression guard. The leaks above were proven red at buffer level against the parent commit.

Verification

  • cargo fmt --all --check
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo test --workspace: 2932 passed, 0 failed
  • tmux suite with a disposable HOME and SHELL=/bin/bash: 93/93
  • n/a python3 scripts/check-secrets.py: this repository has no such script. Fixtures are synthetic.

Independent review

A fresh reviewer read 993754c. Verdict: not ready. The matrix could not see most surfaces, because a pending approval hid the inspector. Resolved in 588e85f:

Finding Resolution
High: the matrix never drew the transcript or the pages Three variants, plus the distinct-frame test
High: error notices raw through Classic's modal Next error panel; delegation updated
Session page model, provider, familiar and project raw field() makes every value inert
Attachment labels raw Made inert in view.rs
Toast covered the approval or inspector title Placed in the transcript pane; skipped while an approval is pending
Delegated Classic frames not covered Named in the test header as a gap; tracked here
The way-back test could pass on the toast's "Esc" Notifications cleared in that test
tmux OSC 52 check satisfied by the pasted prompt The model has its own payload; OSC 8 and BEL checks added
Bidi branch can't fire in buffer tests Documented; it matters on the title path, which filters it
Nits Ellipsis on cut toasts; title call sites reformatted

Accepted gaps:

  • Frames still handed to Classic (suggestions, the effort picker, the familiar switcher, other modals) draw their text and transcript the way Classic does.
  • Direction marks (U+200E/F, U+061C) and line separators are not filtered from the title.

Risk and Rollback

  • Risk level: low to medium. The changes are display only and Next only. Error notices now render through Next instead of Classic.
  • Rollback plan: revert this PR. No data or format migrates.

…eaks (#417)

- New buffer-level suite: one hostile corpus (OSC 52, OSC 8, OSC 0, screen
  clear, cursor, C1, CR, BS, BEL, NUL, bidi, CJK, combining, ZWJ emoji) in
  every untrusted field, drawn on every page at 20x6 to 180x50, in every
  color mode and both glyph sets: no control character reaches a cell, the
  way back stays visible from 40x12, and NO_COLOR draws no color.
- Fix: the notification toast drew its message raw and in hard-coded
  truecolor. Next now draws its own one-row toast in the selected color
  mode with inert text; Classic keeps its banner.
- Fix: the Next header and Session page showed the session title, project,
  model and familiar raw.
- Fix: in Next the terminal-title topic drops control characters, so a
  stored session title cannot end the OSC title sequence early.
- tmux case 11: a fake claude answers with control sequences; tmux paste
  buffers and the title stay clean and earlier screen content survives.
…each every page

- Matrix: three variants (approval pending, plain, error notice); the plain
  variant must draw a distinct frame for every page, so a hidden inspector
  can no longer pass for coverage.
- Error notices: Next draws its own inert panel over the composer instead
  of handing the frame to Classic's modal, which drew provider error text
  raw and in color under NO_COLOR. Esc still dismisses through the shared
  key handling (parity test updated and a new one added).
- Every Next field value is made inert (Session page model, provider,
  familiar and project; approval Project detail).
- Attachment labels (image media type, document title, pasted image file
  name) are made inert.
- The toast sits in the transcript pane, never over an approval, the header
  or the inspector, and ends a cut message with an ellipsis.
- tmux case 11: the model's OSC 52 has its own payload; hyperlink and raw
  BEL checks added.
- Test header names what is not covered: frames still handed to Classic,
  look-alike chrome, and bidi marks.
@vercel

vercel Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
docs Ready Ready Preview Nov 2, 2026 7:19am UTC

Request Review

…xt (#417)

- Every scheme (dark, light) and motion setting (full, reduced) across all
  color modes, pages and sizes keeps cells inert and the way back visible.
- Large content: a 100,000-line answer, a 10,000-line diff-shaped result
  and 64 KiB of binary output render inert at 20x6 to 180x50 with the live
  end and the way back visible; scrolling away and Alt+L back to live work.
- Observation (not a gate): frame cost grows with transcript length.
@BunsDev

BunsDev commented Nov 2, 2026

Copy link
Copy Markdown
Member Author

Added e5146da (tests only, no product change):

  • Appearance matrix: dark and light schemes × full and reduced motion × all color modes, pages and sizes. Every frame keeps cells inert and keeps the way back visible.
  • Large content: next_workload.rs covers a 100,000-line answer, a 10,000-line diff-shaped result and 64 KiB of binary output. All render inert from 20x6 to 180x50, with the live end and the way back visible. Scrolling away and Alt+L back to live work.
  • Observation: frame cost grows with transcript length (about 1.7 ms at 1,000 lines and about 90 ms at 100,000, debug build). Filed as Next renders the whole transcript every frame: cost grows with transcript length #439, together with the missing jump-to-start key.

Workspace: 2936 passed, 0 failed. Clippy is clean.

This branch was successfully deployed

1 active deployment
Preview — e5146da9 Deployed Nov 2, 2026 by vercel[bot]
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