Skip to content

fix: address P0 and P1 code review findings#5

Open
FredHuFuture wants to merge 16 commits intomkurman:mainfrom
FredHuFuture:fix/code-review-p0-p1
Open

fix: address P0 and P1 code review findings#5
FredHuFuture wants to merge 16 commits intomkurman:mainfrom
FredHuFuture:fix/code-review-p0-p1

Conversation

@FredHuFuture
Copy link
Copy Markdown

@FredHuFuture FredHuFuture commented Mar 24, 2026

Summary

Addresses all P0 (critical) and P1 (high priority) findings from code review, one commit per fix:

P0 — Immediate fixes

  • Named Pipe ACL: Restrict \.\pipe\cmux to current user via PipeSecurity / NamedPipeServerStreamAcl.Create
  • TerminalSession ReadLoop/Dispose race: Introduce CancellationTokenSource and acquire _readStream under lock; join read thread on Dispose
  • TerminalProcess wait thread not joined: Use WaitForMultipleObjects with a cancel event; signal + join on Dispose
  • VtParser OSC/CSI length limits: Cap OSC at 64 KB, CSI params at 256 to prevent OOM from malicious streams
  • CLI test coverage: Add 24 tests for ParseArgs, CLI commands, NamedPipeServer ParseArgs, and VtParser limits

P1 — High priority fixes

  • Thread safety: Add lock synchronization to ScrollbackBuffer, TerminalSelection, and TerminalBuffer
  • UI memory leaks: Unsubscribe SettingsChanged in MainWindow; unsubscribe WorkingDirectoryChanged in WorkspaceViewModel
  • SecretStoreService integrity: Add HMAC-SHA256 verification and DPAPI entropy; backward-compatible with legacy format
  • DaemonClient race: Replace volatile check-then-act on _pendingResponse with a dedicated lock
  • TerminalProcess CreateProcess failure: Add _processCreated guard on all property accessors; clean up on failure
  • AgentRuntimeService fire-and-forget: Observe task exceptions via ContinueWith(OnlyOnFaulted) to prevent silent failures

Additional fixes (from Codex review & testing)

  • SecretStore legacy fallback: Fixed blob.Length > 32 misclassifying legacy DPAPI secrets as new-format; now catches decryption failure and falls back to legacy Unprotect(..., null, ...) path
  • VtParser OSC overflow: Fixed parser jumping to Ground state when OSC exceeds max length; now remains in OscString state to consume remaining bytes until BEL/ST terminator arrives
  • CJK/full-width character rendering: Added UnicodeWidth utility for detecting wide characters (CJK, fullwidth forms, etc.); TerminalBuffer.WriteChar now advances cursor by 2 columns and places a padding cell for wide chars; TerminalControl rendering skips padding cells and draws wide characters at double width — fixes Chinese/Japanese/Korean text display where cursor position was offset

Test plan

  • Verify named pipe rejects connections from other users
  • Verify terminal sessions start/stop cleanly without thread leaks
  • Run new CLI and VtParser limit tests (dotnet test)
  • Verify existing secrets still readable (legacy format fallback)
  • Confirm daemon client request/response flow works under load
  • Verify UI windows can be opened/closed repeatedly without memory growth
  • Verify Chinese/Japanese/Korean characters display correctly with proper cursor alignment

The named pipe \.\pipe\cmux was created without any ACL, allowing
any process on the machine to connect. Add PipeSecurity that grants
FullControl only to the current Windows user via
NamedPipeServerStreamAcl.Create.
_readStream.Read() was called outside the lock while Dispose could
simultaneously null out _readStream. Introduce a CancellationTokenSource
to coordinate shutdown: the ReadLoop now checks the token and acquires
_readStream under lock. Dispose signals the CTS, disposes streams under
lock, then joins the read thread with a timeout.
The Dispose method killed the process but never joined _waitThread,
allowing it to access already-freed handles. Replace
WaitForSingleObject with WaitForMultipleObjects that monitors both
the process handle and a cancel event. Dispose now signals the cancel
event and joins the thread before closing handles.
_oscString had no upper bound, allowing a malicious byte stream to
cause OOM. Cap OSC strings at 64 KB and abort the sequence if
exceeded. Limit CSI parameters to 256 entries and cap the raw param
accumulation buffer proportionally.
The CLI had zero test coverage. Add tests for:
- CLI ParseArgs: long/short options, boolean flags, positional args,
  mixed arguments, empty input
- CLI Main: help, version, unknown commands, missing subcommands
- NamedPipeServer ParseArgs: key=value, JSON, quoted values, positional
- VtParser limits: OSC exceeding 64KB, CSI exceeding 256 params

Make CLI ParseArgs internal with InternalsVisibleTo for testability.
…rminalBuffer

ScrollbackBuffer: wrap all public methods (Add, AddRange, Clear,
indexer, ToList, Count) in lock.

TerminalSelection: synchronize Start/Extend/Clear/SelectWord/
SelectLine/SelectAll and property accessors with lock.

TerminalBuffer: expose SyncRoot and add lock to GetScrollbackLine,
ExportPlainText, CreateSnapshot, and RestoreSnapshot which can be
called from the UI thread while the parser runs on the read thread.
MainWindow: unsubscribe SettingsService.SettingsChanged in OnClosing
(was subscribed in constructor but never unsubscribed).

WorkspaceViewModel: unsubscribe surface WorkingDirectoryChanged when
closing a surface and in Dispose to prevent leaked references.
DPAPI encryption previously used null optionalEntropy, and stored
ciphertext had no integrity check. Now:
- Use application-specific entropy bytes for DPAPI Protect/Unprotect
- Prepend HMAC-SHA256 of the ciphertext to each stored value
- Verify HMAC with constant-time comparison on read
- Fall back to legacy format (no HMAC, no entropy) for existing secrets
The volatile check-then-act on _pendingResponse was not atomic: the
listen loop could read it while SendRequestAsync was swapping it.
Introduce a dedicated _pendingLock to synchronize all reads and writes
of _pendingResponse across the listen loop and request path.
Add _processCreated flag to guard all property accessors (ProcessId,
ProcessHandle, HasExited) and Kill against using uninitialized
_processInfo values. Clean up attribute list before throwing when
CreateProcess fails.
…get task

The discarded Task.Run result (_ = Task.Run(...)) could silently
swallow exceptions thrown outside the inner try-catch (e.g., during
lambda capture or task scheduling). Add ContinueWith with
OnlyOnFaulted to observe and log any unhandled task exceptions.
@FredHuFuture FredHuFuture force-pushed the fix/code-review-p0-p1 branch from 39f302d to 957b27f Compare March 24, 2026 05:07
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 39f302dfa7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

blob.Length > 32 also matches legacy DPAPI ciphertext, so existing
secrets were misclassified as new-format and returned null on HMAC
mismatch. Catch CryptographicException from new-format Unprotect and
fall through to legacy Unprotect(..., null, ...) path.

Addresses codex review feedback on SecretStoreService.
Previously the parser jumped to Ground on oversized OSC, causing
remaining OSC bytes to be interpreted as normal terminal input. Now
the parser stays in OscString and silently discards excess bytes
until BEL/ST terminates the sequence.

Addresses codex review feedback on VtParser.
Add UnicodeWidth utility for detecting wide (2-column) characters
such as CJK ideographs, Hangul syllables, and fullwidth forms.

TerminalBuffer.WriteChar now sets Width=2 for wide characters, places
a padding cell (Width=0) in the next column, and advances the cursor
by 2. Early line-wrap is triggered when a wide char won't fit.

TerminalControl rendering skips padding cells, draws double-width
backgrounds for wide cells, and tracks column span instead of
character count for underline/strikethrough decoration width.
Log PseudoConsole creation, process PID, and ReadLoop lifecycle events
to cmux-diag.log for easier debugging of terminal startup failures.
@mkurman
Copy link
Copy Markdown
Owner

mkurman commented Mar 31, 2026

@copilot code review

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses previously identified P0/P1 review findings across IPC security, terminal stability/thread-safety, secret storage integrity, VT parsing hardening, CJK rendering, and adds/expands test coverage for CLI and parser limits.

Changes:

  • Hardened IPC + process/session lifecycle: restricted named pipe ACLs; reduced shutdown races by adding cancellation/join semantics in terminal threads.
  • Defensive parsing + rendering: added VT parser limits and Unicode wide-character handling across buffer + renderer.
  • Reliability/security improvements + tests: added secret store verification logic and expanded CLI/parser unit tests.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
tests/Cmux.Tests/CoreTests.cs Adds CLI, named-pipe arg parsing, and VT parser limit tests.
tests/Cmux.Tests/Cmux.Tests.csproj References Cmux.Cli so CLI internals can be tested.
src/Cmux/Views/MainWindow.xaml.cs Unsubscribes SettingsChanged on window close to prevent leaks.
src/Cmux/ViewModels/WorkspaceViewModel.cs Unsubscribes WorkingDirectoryChanged and disposes surfaces more safely.
src/Cmux/Services/AgentRuntimeService.cs Observes fire-and-forget task failures via faulted continuation.
src/Cmux/Controls/TerminalControl.cs Updates rendering to support wide characters and skip padding cells.
src/Cmux.Core/Terminal/VtParser.cs Adds OSC/CSI size limits to reduce memory risk from malicious streams.
src/Cmux.Core/Terminal/UnicodeWidth.cs Introduces a wcwidth-like helper for wide/fullwidth character detection.
src/Cmux.Core/Terminal/TerminalSession.cs Adds cancellation/join in dispose path; adds diagnostic logging.
src/Cmux.Core/Terminal/TerminalSelection.cs Adds locking around selection state for thread-safety.
src/Cmux.Core/Terminal/TerminalProcess.cs Joins wait thread on dispose; adds cancel event + multi-wait.
src/Cmux.Core/Terminal/TerminalBuffer.cs Adds synchronization hooks and wide-character cell handling.
src/Cmux.Core/Terminal/ScrollbackBuffer.cs Adds locking around buffer operations for thread-safety.
src/Cmux.Core/Terminal/ConPtyInterop.cs Adds P/Invoke declarations for WaitForMultipleObjects + events.
src/Cmux.Core/Services/SecretStoreService.cs Adds HMAC + DPAPI entropy and legacy fallback handling.
src/Cmux.Core/IPC/NamedPipeServer.cs Restricts named pipe access to current user via PipeSecurity.
src/Cmux.Core/IPC/DaemonClient.cs Replaces volatile check-then-act on pending response with a lock.
src/Cmux.Core/Cmux.Core.csproj Adds System.IO.Pipes.AccessControl package dependency.
src/Cmux.Cli/Program.cs Exposes ParseArgs internally for unit testing.
src/Cmux.Cli/Cmux.Cli.csproj Adds InternalsVisibleTo for Cmux.Tests.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

…afety

- VtParser: add _oscOverflow flag to suppress dispatch for overflowed OSC sequences
- TerminalProcess: validate CreateEventW return, handle WAIT_FAILED in WaitForMultipleObjects
- SecretStoreService: remove trivially-forgeable HMAC (public key), rely on DPAPI integrity with entropy; keep backward compat for legacy + HMAC formats
- TerminalSession: gate diagnostic logging behind CMUX_DIAG env var, add 2MB log rotation
- TerminalControl: lock buffer.SyncRoot during Render to prevent torn reads
- CoreTests: strengthen OSC overflow test to assert no dispatch, add positive case
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.

3 participants