Repository navigation
v0.22 hardening: input ownership by lifecycle, not vocabulary (#426); stable shell terminal size (#443) - #457
Merged
Merged
Conversation
…ommand by itself (#426) The #448 rule decided by the text: a line typed while a command ran was held only if its first word was not a command the shell knows. A reply that is itself a command name (`yes`, `ls`, `sudo`), a one-time code or an assignment still ran when its read gave up unseen. Ownership, not vocabulary, now decides: - Withheld lines. A plain Enter while a command runs, with no question NMSh saw, queues the line with `typedWhile`: the owner (service or in-process) forces it to wait for approval and refuses to clear that, frontends show it as "(typed while <program> ran)" until the person presses Tab, and it is never offered by Up. Ctrl+Q stays the explicit run-after. Keys held for a look whose command ended are replayed with the same rule, so their Enter never submits at the prompt. A held head no longer blocks a command submitted at the prompt. - Delivered bytes. Input NMSh wrote for a program that never read it stayed in the terminal: the shell ran a whole line as its next command and glued a partial one onto the next submission (`QZSECRETecho NEXT`). When NMSh delivered input to a command (a flag file, written before the bytes), the shell's first prompt hook drains the terminal (stty -icanon min 0 time 0, head, od) and reports it as an authenticated `unread` marker; the owner holds each line for approval. Input arriving after the done marker is held, never written. zsh, Bash and Fish. - Stop window. A line entered right after Ctrl+C or Ctrl+Z is held, never written, until the outcome is verified: the prompt (it runs there), a question asked after the stop (its reply), or the window ending with the program alive or having asked (held for approval). - Older services (no look result) fail closed: typing with no evidence stays held and hidden until Ctrl+S, Tab or Esc, and the notice says so. Tests first: inputQuarantine (19 fail on fa7ec66), queueQuarantine, unreadInputLive; live residual test with `ls` ran in zsh, Bash and Fish on fa7ec66. Queue-mechanics live tests queue with Ctrl+Q; tests that typed ahead with Enter now approve the held line.
…dow does (#443) Every resize of the shell's PTY sends SIGWINCH to the foreground job, and Fish 3 drops a terminal write that signal interrupts ("write: Interrupted system call"). The PTY's rows were the transcript capacity of the current frame, so a command starting or ending (the activity row), a growing composer, a completion menu, a notice, the inspector or Settings each resized it: six resizes in one ordinary sequence of the new deterministic test on fa7ec66. The rows now come from a reference layout for the window and settings (a running command's activity row, a one-line composer, no menu, inspector, notice or panel), and while a command runs only a real window change (onResize, or the end of a full-screen handover, both reset lastPtyRows) or a width change resizes it. The transcript region may be a few rows shorter than the terminal, as under Dock Top or with the queue list: the newest rows show. Full-screen programs keep the whole window. Tests: ptyStableSize (deterministic, fails on fa7ec66), ptyResizeLive (SIGWINCH counted by a real foreground program in zsh, Bash and Fish), and fishOutputIntegrityLive for the Fish 3.7 CI loop.
… for the prompt (#426, #436) With lines entered while a command runs held for approval, a command typed during a slow prompt hook (after the command ended, before its prompt) was held too, though #436 (physical QA A10 step 3) made it run once at the prompt. Only the shell's authenticated done marker proves the command is over, and a look's "idle" dates the look, not the key: a reply begun just before an unseen read gave up would pass for type-ahead. The input state now carries `done` from the shell's done marker (an optional field older windows ignore). A draft begun after the window received it is held, never written, until the verified prompt, and runs there once. A draft begun before is held for approval as before. Older services send no `done`, so such lines are held (fail closed). Tests: two deterministic cases (after-done type-ahead runs at the prompt; a draft begun before done is held), the done marker on the wire, and a live zsh/Bash test that types and enters a command during a slow prompt hook.
…or it; held-back reports apply first (#426) When the stop window ends with the program still running, the lines held since Ctrl+C go to it, as a terminal delivers type-ahead: on macOS NMSh cannot see a read after Ctrl+C, because the terminal's echoed ^C ends the question-shaped line. What it never reads is taken back at the end of the command and held for approval, so the line still never runs as a command. A question report held back during the window applies before the window settles, whatever order the two timers have. Live test: a bash survivor answered with the line typed right after Ctrl+C. QA and changelog: R4 rewritten, R5-R7 replace A9, A10, A1 (steps 1-3, 6) and B1 for the hardened candidate; fixtures for a timed prompt and a survivor. The changelog describes the new routing, its limits and Fish 3.
…anel PTY rows follow the reference layout (#426, #443) Under the new routing a line typed while a command still runs waits for approval, so live tests that type the next command right after starting one (an approval, an answer, a full-screen program's exit) raced its prompt on slower CI runners. Frontend.run now waits for the shells to be idle first; tests that mean to queue use Ctrl+Q. The PTY-row tests assert the reference layout (a panel opening or closing never resizes the shell's terminal).
…kes the next command's keys (#426) After the Enter that answers a hidden line (or the key that answers a key read), a report of that same question the session sampled before it saw the answer could arrive afterwards and put typing back into direct mode: the first keys of the next command went to the program, were never read, and were taken back as a held fragment while the rest reached the composer (seen in CI as a split type-ahead in typingAfterRead). A report of a question no newer than the one just answered is stale and is dropped; a new question starts later. Ctrl+C and Ctrl+Z are no answer: a program that reads through them still asks (#436).
…ext; mux tests wait for each prompt (#426) typingAfterRead decided whether its next command was held from screen text; a redraw repeats older rows and notes, so a stale "did not read" note read as a new held line (CI). It now waits for the read's journaled completion and asks the session's queue. The idle wait in Frontend.run applies only to a sandbox's single session. The GNU screen test's own run helper waits for each command's completion row before typing the next.
… the #443 claim it supports
raiseCatError
added a commit
that referenced
this pull request
Oct 19, 2026
This was referenced Oct 19, 2026
Merged
Flaky under parallel load: a command typed right after Ctrl+C on a waiting read can fail to run
#436
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Final v0.22 security and reliability pass, on
release/v0.22.0=fa7ec665. Part of #444. Draft: physical QA and the release decision stay with the maintainer.#426: nothing typed while a command runs becomes a shell command by itself
Replaces #448's vocabulary rule. That rule held a line only when its first word wasn't a command the shell knows, so a reply like
yes,ls,sudoorX=1still ran when its read gave up unseen. Three lifecycle layers now decide instead:typedWhile). The owner (session service or in-process client) enforces this: such a line can't be set to run by itself. Every queue surface shows it as(typed while make ran)until Tab, and Up never offers it. Ctrl+Q is the explicit run-after. Keys held during an ownership look whose command ended follow the same rule. Decided with the maintainer.stty -icanon min 0 time 0,headandod), and the owner holds it for approval. Input that arrives after the done marker is never written. zsh, Bash and Fish.#443: NMSh's own layout never resizes the shell's terminal
The terminal's rows used to follow the current frame, so a command starting or ending, the composer growing, a menu, a notice or Settings each sent SIGWINCH. Fish 3 drops a write that signal interrupts. The rows now come from a reference layout for the window and settings. While a command runs, only a real window change resizes the terminal.
Evidence (local, macOS, Fish 4.9.3; Linux Fish 3.7 loop in run 39283359699)
fa7ec665inputQuarantine(deterministic)lsbefore an unseen timed readecho QZ-RAN⏎+ partialQZSECRETecho QZ-RAN, thenQZSECRETecho NEXTptyStableSize: UI-only resizes in one sequenceptyResizeLive: SIGWINCH from chrome / from 1 window resizeRemaining limits are in the CHANGELOG's Known limitations, and the ledger has the full record.