This release includes 12 security fixes for security teams reviewing exposed deployments.
Topics
+12 more
Affected surfaces
Summary
AI summaryBroad release touches mcp, fix, ci, and company.
Full changelog
The big batch since v2.16.2. Headline: a security-hardening sweep across the daemon, MCP, A2A, release pipeline, and browser surfaces — most of it surfaced by an external codex security scan, with each finding triaged and adversarially verified before merge (a chunk turned out to be false-positives or duplicates and were closed rather than merged). Plus a set of fixes that make the embedded browser tools work on packaged builds, per-workspace environment/startup profiles, and the workspace-management UX that profiles implied (duplicate, per-terminal working directories). No config changes required — defaults are unchanged.
Added
- Per-workspace process profiles. Each workspace can define environment variables and an optional startup command, applied to new panes only — existing and recovered daemon PTYs keep their create-time environment. Right-click a workspace → "Configure profile…". Generic by design (no provider hardcoding): point
CLAUDE_CONFIG_DIR,CODEX_HOME, SSH wrappers, etc. at different accounts per workspace. This is environment separation, not an OS-level security sandbox. See docs/workspace-profiles.md for setup and multi-account recipes. (#101, #103) - Workspace management actions. Right-click a workspace to duplicate it — the layout (fresh pane/surface ids, cleared ptyIds so new panes spawn their own PTYs) and the profile (re-normalized through the secret-name policy) are cloned as
<name> (copy N). A new Working directories menu lists each terminal's live cwd with copy; every terminal now tracks its own cwd (shown in the tab tooltip), terminal tabs can be renamed (double-click), and an accidental workspace close is guarded by a confirmation. New-pane semantics throughout, consistent with the profile contract. (#141)
Security
- Token-file ACL grants by the labeled SID.
getCurrentUserSidparsed the first SID-shaped substring ofwhoamioutput, so a SID-shaped account or machine name (e.g.S-1-1-0= Everyone) could be granted the auth-token ACL instead of the real owner, leaving the token world-readable. It now parses the explicitSID:field. (#118) - Token-file DACL is rebuilt owner-only, even on the upgrade path. The shipped
icacls /grant:r … /inheritance:ronly replaced the named principal's ACE and stripped inherited ACEs, so a pre-existing explicit broad ACE (e.g.Everyone:(R)from a redirected/roamed/MDM profile) survived and left the token world-readable. The DACL is now rebuilt with a .NET DACL-only primitive (no owner/group/SACL writes, so it needs no privilege and succeeds on the upgrade-from-icacls state), with icacls as a fail-closed fallback when PowerShell is blocked. (#140) - MCP approvals bind to the reviewed capability snapshot. A plugin could redeclare broader capabilities while an approval prompt was pending and get trusted for a set the user never saw (a TOCTOU between consent and call). The approval now pins the exact capabilities shown in the dialog. (#122)
- Terminal drops are restricted to wmux drag sources. Text dragged from a browser or another app no longer routes straight into a terminal PTY (where embedded newlines could auto-run at a shell prompt); only internal wmux drags — sidebar, surface tabs, file tree — write to the pane. (#123)
- Default MCP terminal resolution fails closed. A spoofable
WMUX_WORKSPACE_IDenv hint or a failed workspace claim could fall back to the user's active pane, i.e. cross-workspace keystroke injection/read. Terminal tools now require a verified, PID-mapped identity and refuse the env hint, throwing rather than touching the focused pane. (#125) - A2A sender identity is authoritative. Company A2A
send/broadcastno longer accept a caller-suppliedfrom; the sender is derived from the authenticated workspace, so one agent can't impersonate another (or the CEO) when delivering a message into a peer's terminal. (#129) - Inter-agent PTY delivery is bracketed-paste wrapped. A2A messages written into a peer's terminal are bracketed and ESC-sanitized so an embedded newline can't submit a command in the receiving shell. (#132)
- Remote SOUL prompt loading is disabled. Company agent personas were fetched from a third-party URL at spawn and written verbatim into the agent's instruction file — a remote prompt-injection / supply-chain path into command-capable agents. Spawning now uses the built-in role prompts only. (#131)
- RPC browser profile switching is scoped. An RPC caller could mount an arbitrary Electron persistent partition or the human's pre-seeded
loginsession store (reading its cookies over CDP). Profile names are now validated and RPC selection is restricted to a safe allowlist. (#133) - IPv6 navigation SSRF hardening. The navigation URL validator now un-brackets and bit-masks IPv6 literals (unique-local, link-local, IPv4-mapped), closing a bracket bypass that reached internal addresses through the
browser_tabsnew-tab path. (#137) - Bundled first-party MCP server runs under enforce mode. Under packaged enforce mode the bundled server was denied because it never went through declare/approve, so wmux's own tools were locked out. A name-recognized, scoped allowlist lets the first-party tools run without opening the gate to third-party servers. (#109)
- Release pipeline hardening. The release tag is passed through
env:instead of being interpolated into a shellrun:block (Actions script-injection); the SignPath token is scoped to the signing step instead of the whole job; third-party release actions are pinned to immutable commit SHAs; WinGet publishing moved to a least-privilege job; and the installer fails closed when the checksum manifest is missing or invalid. (#119, #120, #121, #126, #135) - Recursive IPC error-log redaction. The structured IPC error logger now redacts sensitive keys at any depth, redacts startup-command values, and summarizes env maps to a key count — so workspace-profile env/commands flowing through
pty:createcan never leak intoargs_summary. Profile env is also kept out of the copy-session-info / drag-export markdown, and reservedWMUX_*keys are rejected so a profile can't spoof workspace identity. (#103) - Child shells never inherit a stale wmux identity. A wmux launched from inside a wmux pane (e.g.
npm startwhile dogfooding) inherited the parent pane'sWMUX_WORKSPACE_ID/WMUX_SURFACE_ID/WMUX_SOCKET_PATHin its own environment, which could survive into freshly created child shells. The whole reservedWMUX_*namespace is now cleared from the spawn baseline before identity is forced, so a child's identity is only ever what wmux explicitly sets — the spoofing guarantee is now unconditional, not profile-only. (#141)
Fixed
- Embedded browser tools work on packaged builds. On packaged builds
getPage()can't surface the<webview>guest as a PlaywrightPage, so a swath of browser tools failed withNo browser page available. They now fall back to the main-process CDP/RPC channel: DOM tools read the real webview instead of the wmux app shell (#104), extraction/snapshot (#105), console/network/response-body capture (#106),browser_extract_datafield mapping (#110), cookies/storage/emulate/resize (#111), geolocation grants + reset semantics (#112), andbrowser_wait(#114).browser_open/session_startroute throughrequireWorkspaceIdso the browser opens in the calling workspace, not the active one (#96). - Memory-leak audit survivors. Three real leaks found in a leak audit are now bounded: the MCP capture buffer (a Page-keyed WeakMap), the A2A GC hard cap, and PTY listener cleanup. (#102)
- Per-terminal working directory is reported correctly (local and daemon mode). The tab tooltip and the workspace "Working directories" menu showed each shell's startup home directory (e.g.
C:\Users\me) for every PowerShell regardless of where it hadcd'd. Two compounding parser bugs are fixed: OSC 7 left Windows paths as/C:/Users/me(leading slash, forward slashes), and prompt detection matched the stale echoed prompt and froze the reported cwd at startup. Parsing is extracted into a unit-testedcwdDetectmodule (shared by both spawn paths) that normalizes the OSC 7 URI to a native path — including UNC shares — and reads the live (last) prompt. Daemon mode additionally never forwarded its detected cwd to the renderer; a newsession:cwdevent now closes that gap so daemon-backed panes live-update like local ones. (#141) - Tighter workspace right-click menu. The context menu was pinned to a fixed minimum width, leaving a wide blank gutter beside short items (and an oversized gap before the "Working directories" submenu arrow); it now sizes to its content. (#141)
Contributors
This release leaned on the community — two external contributors landed real features and fixes, not just reports.
@junbeom09 (조준범) carried forward the packaged-build hardening he started in 2.16.2. Dogfooding the packaged app, he found the browser DOM tools were silently reading the wmux app shell instead of the embedded <webview> — a bug that never reproduces in a dev build — and contributed the runtime shell-detection fix (#104). He then verified the CDP capture and geolocation fallbacks (#108/#112) on a real install, confirming the exact paths CI can't prove. Fixes and reports from real-world setups a single maintainer never sees are how wmux gets more robust.
@snowyukitty had the busiest release of anyone. He built per-workspace process profiles end to end (#101), then followed up after review with path-pointer credential-var allowlisting and non-destructive profile loading so an existing profile is never clobbered on load (#103). He shipped the workspace-management UX that profiles implied — duplicate workspace, the working-directories menu, per-terminal cwd tracking, tab rename, and close confirmation — and fixed the OSC 7 / prompt-detection cwd bugs and the child-shell identity-inheritance leak along the way (#141). He also split the Vitest runtime lane (#97) so timing-sensitive tests run serially instead of flaking under parallel load.
The security-hardening sweep (#118–#137) was surfaced by an external codex security scan; each of the 20-plus findings was triaged and adversarially verified before landing, with false-positives and duplicates closed rather than merged. The token-file ACL rebuild (#118 plus the DACL-only primitive in #140) was additionally dogfooded against a real %USERPROFILE%\.wmux descriptor — a directory that grants SYSTEM and Administrators inherited FullControl — to confirm the hardened token comes out owner-only with no self-lockout.
Maintained by @openwong2kim, with engineering and code-review pairing by Claude (Anthropic). Thanks to everyone filing issues and dogfooding. 🙏
What's Changed
- fix(mcp): route browser_open/session_start through requireWorkspaceId by @openwong2kim in https://github.com/openwong2kim/wmux/pull/96
- test: serialize runtime tests by @snowyukitty in https://github.com/openwong2kim/wmux/pull/97
- fix: memory-leak audit survivors — bound MCP capture, A2A GC hard cap, PTY listener cleanup by @openwong2kim in https://github.com/openwong2kim/wmux/pull/102
- feat: per-workspace process profiles (env + startup command for new panes) by @snowyukitty in https://github.com/openwong2kim/wmux/pull/101
- fix(profile): allowlist path-pointer credential vars; non-destructive load (follow-up to #101) by @snowyukitty in https://github.com/openwong2kim/wmux/pull/103
- fix(mcp): browser DOM tools read app shell instead of webview in packaged builds by @junbeom09 in https://github.com/openwong2kim/wmux/pull/104
- fix(mcp): RPC fallback for browser extraction/snapshot tools in packaged builds (#105) by @openwong2kim in https://github.com/openwong2kim/wmux/pull/107
- feat(mcp): RPC fallback for browser_console/network/response_body in packaged builds (#106) by @openwong2kim in https://github.com/openwong2kim/wmux/pull/108
- fix(mcp): allow the bundled first-party MCP server under enforce mode by @openwong2kim in https://github.com/openwong2kim/wmux/pull/109
- fix(mcp): browser_extract_data field mapping (#110) + packaged RPC fallback for state tools (#111) by @openwong2kim in https://github.com/openwong2kim/wmux/pull/112
- fix(mcp): CDP fallback for browser_wait in packaged builds (#114) by @openwong2kim in https://github.com/openwong2kim/wmux/pull/115
- security: parse whoami SID field for ACL grants by @openwong2kim in https://github.com/openwong2kim/wmux/pull/118
- fix(install): fail closed when checksum manifest missing or invalid by @openwong2kim in https://github.com/openwong2kim/wmux/pull/121
- fix(mcp): bind approvals to capability snapshot by @openwong2kim in https://github.com/openwong2kim/wmux/pull/122
- fix: restrict terminal text drops to wmux drags by @openwong2kim in https://github.com/openwong2kim/wmux/pull/123
- fix(company): authorize A2A sender identity by @openwong2kim in https://github.com/openwong2kim/wmux/pull/129
- fix(company): disable remote SOUL prompt loading by @openwong2kim in https://github.com/openwong2kim/wmux/pull/131
- fix: bracket inter-agent PTY message delivery by @openwong2kim in https://github.com/openwong2kim/wmux/pull/132
- security: harden IPv6 navigation URL validation by @openwong2kim in https://github.com/openwong2kim/wmux/pull/137
- fix(ci): avoid release tag shell injection by @openwong2kim in https://github.com/openwong2kim/wmux/pull/119
- fix(ci): scope SignPath token to signing action by @openwong2kim in https://github.com/openwong2kim/wmux/pull/120
- Harden WinGet release publishing by @openwong2kim in https://github.com/openwong2kim/wmux/pull/126
- ci: pin release workflow actions to immutable SHAs by @openwong2kim in https://github.com/openwong2kim/wmux/pull/135
- Restrict RPC browser profile switching by @openwong2kim in https://github.com/openwong2kim/wmux/pull/133
- fix(mcp): fail-closed default PTY resolution and verify workspace identity by @openwong2kim in https://github.com/openwong2kim/wmux/pull/125
- Workspace UX: duplicate, per-terminal cwd & working-dirs menu — plus cwd/identity fixes by @snowyukitty in https://github.com/openwong2kim/wmux/pull/141
- fix(security): rebuild Windows token DACL with a DACL-only primitive by @openwong2kim in https://github.com/openwong2kim/wmux/pull/140
New Contributors
- @snowyukitty made their first contribution in https://github.com/openwong2kim/wmux/pull/97
Full Changelog: https://github.com/openwong2kim/wmux/compare/v2.16.2...v2.17.0
Security Fixes
- Token‑file ACL now parses explicit SID field instead of arbitrary substring
- Token‑file DACL rebuilt owner‑only using .NET primitive with icacls fallback
- MCP approvals pin reviewed capability snapshot to prevent TOCTOU
- Terminal text drops limited to wmux drag sources
- Default MCP terminal resolution requires verified PID‑mapped identity
- A2A sender identity derived from authenticated workspace, disallowing caller‑supplied `from`
- Inter‑agent PTY delivery bracketed and ESC‑sanitized
- Remote SOUL prompt loading disabled; built‑in role prompts used instead
- RPC browser profile switching validated against allowlist
- IPv6 navigation URL validator masks unique‑local, link‑local and IPv4‑mapped addresses
- Bundled first‑party MCP server allowed under enforce mode via scoped allowlist
- Release pipeline hardened: tag handling, SignPath scoping, pinned actions, WinGet publishing least‑privilege, checksum failure closed
Weekly OSS security release digest.
The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.
No spam, unsubscribe anytime.
Share this release
About Wmux
All releases →Related context
Beta — feedback welcome: [email protected]