@wong2kim/wmux 1.1.2 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +137 -86
- package/dist/cli/cli/commands/system.js +12 -1
- package/dist/cli/shared/rpc.js +6 -0
- package/dist/cli/shared/types.js +108 -4
- package/dist/mcp/mcp/index.js +12 -1
- package/dist/mcp/mcp/playwright/PlaywrightEngine.js +193 -86
- package/dist/mcp/mcp/playwright/anti-detection.js +12 -7
- package/dist/mcp/mcp/playwright/security.js +29 -0
- package/dist/mcp/mcp/playwright/tools/extraction.js +3 -3
- package/dist/mcp/mcp/playwright/tools/file.js +4 -4
- package/dist/mcp/mcp/playwright/tools/inspection.js +93 -29
- package/dist/mcp/mcp/playwright/tools/interaction.js +207 -137
- package/dist/mcp/mcp/playwright/tools/navigation.js +37 -14
- package/dist/mcp/mcp/playwright/tools/state.js +4 -4
- package/dist/mcp/mcp/playwright/tools/utility.js +2 -2
- package/dist/mcp/mcp/playwright/tools/wait.js +10 -2
- package/dist/mcp/shared/rpc.js +6 -0
- package/dist/mcp/shared/types.js +108 -4
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -1,112 +1,158 @@
|
|
|
1
|
-
# wmux
|
|
1
|
+
# wmux — AI Agent Terminal for Windows
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
> **Run Claude Code + Codex + Gemini CLI side by side.**
|
|
4
|
+
> Split terminals, browser automation, MCP integration — the only proper way to use AI agents on Windows.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
[](https://github.com/openwong2kim/wmux/releases/latest)
|
|
7
|
+
[](https://www.npmjs.com/package/@wong2kim/wmux)
|
|
8
|
+
[](https://www.electronjs.org/)
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
[](https://github.com/openwong2kim/wmux)
|
|
6
11
|
|
|
7
|
-
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Still using one terminal for your AI coding agents on Windows?
|
|
15
|
+
|
|
16
|
+
macOS has [cmux](https://github.com/manaflow-ai/cmux) — a tmux-based terminal multiplexer for AI agents.
|
|
17
|
+
|
|
18
|
+
**Windows has no tmux.** Without WSL, there was no way.
|
|
8
19
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
20
|
+
wmux fixes this. Native Windows terminal multiplexer + browser automation + MCP server. Your AI agent reads the terminal, controls the browser, and works autonomously.
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
Claude Code writes the backend on the left
|
|
24
|
+
Codex builds the frontend on the right
|
|
25
|
+
Gemini CLI runs tests at the bottom
|
|
26
|
+
— all on one screen, simultaneously.
|
|
27
|
+
```
|
|
12
28
|
|
|
13
29
|
---
|
|
14
30
|
|
|
15
|
-
## Install
|
|
31
|
+
## Install in 30 seconds
|
|
32
|
+
|
|
33
|
+
**Installer:**
|
|
16
34
|
|
|
17
|
-
|
|
35
|
+
[Download wmux Setup.exe](https://github.com/openwong2kim/wmux/releases/latest)
|
|
18
36
|
|
|
19
|
-
|
|
37
|
+
**One-liner (PowerShell):**
|
|
20
38
|
```powershell
|
|
21
39
|
irm https://raw.githubusercontent.com/openwong2kim/wmux/main/install.ps1 | iex
|
|
22
40
|
```
|
|
23
41
|
|
|
42
|
+
**npm (CLI + MCP server):**
|
|
43
|
+
```bash
|
|
44
|
+
npm install -g @wong2kim/wmux
|
|
45
|
+
```
|
|
46
|
+
|
|
24
47
|
---
|
|
25
48
|
|
|
26
49
|
## Why wmux?
|
|
27
50
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
51
|
+
### 1. Your AI agent controls the browser — for real
|
|
52
|
+
|
|
53
|
+
Tell Claude Code "search Google for this" and it actually does it.
|
|
54
|
+
|
|
55
|
+
wmux's built-in browser connects via Chrome DevTools Protocol. Click, type, screenshot, execute JS — all done by the AI directly. Works perfectly with React controlled inputs and CJK text.
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
You: "Search for wmux on Google"
|
|
59
|
+
Claude: browser_open → browser_snapshot → browser_fill(ref=13, "wmux") → browser_press_key("Enter")
|
|
60
|
+
→ Actually searches Google. Done.
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 2. Multiple terminals in one window
|
|
64
|
+
|
|
65
|
+
`Ctrl+D` to split, `Ctrl+N` for new workspace. Place multiple terminals and browsers in each workspace. `Ctrl+click` for multiview — see multiple workspaces at once.
|
|
66
|
+
|
|
67
|
+
ConPTY-based native Windows terminal. xterm.js + WebGL hardware-accelerated rendering. 999K lines of scrollback. Terminal content persists even after restart.
|
|
68
|
+
|
|
69
|
+
### 3. No more asking "is it done yet?"
|
|
70
|
+
|
|
71
|
+
wmux tells you when your AI agent finishes.
|
|
72
|
+
|
|
73
|
+
- Task complete → desktop notification + taskbar flash
|
|
74
|
+
- Abnormal exit → immediate warning
|
|
75
|
+
- `git push --force`, `rm -rf`, `DROP TABLE` → dangerous action detection
|
|
76
|
+
|
|
77
|
+
Not pattern matching — output throughput-based detection. Works with any agent.
|
|
78
|
+
|
|
79
|
+
### 4. Automatic Claude Code integration
|
|
80
|
+
|
|
81
|
+
Launch wmux and the MCP server registers automatically. Claude Code just works:
|
|
82
|
+
|
|
83
|
+
| What Claude can do | MCP Tool |
|
|
84
|
+
|---|---|
|
|
85
|
+
| Open browser | `browser_open` |
|
|
86
|
+
| Navigate to URL | `browser_navigate` |
|
|
87
|
+
| Take screenshot | `browser_screenshot` |
|
|
88
|
+
| Read page structure | `browser_snapshot` |
|
|
89
|
+
| Click element | `browser_click` |
|
|
90
|
+
| Fill form | `browser_fill` / `browser_type` |
|
|
91
|
+
| Execute JS | `browser_evaluate` |
|
|
92
|
+
| Press key | `browser_press_key` |
|
|
93
|
+
| Read terminal | `terminal_read` |
|
|
94
|
+
| Send command | `terminal_send` |
|
|
95
|
+
| Manage workspaces | `workspace_list` / `surface_list` / `pane_list` |
|
|
96
|
+
|
|
97
|
+
**Multi-agent:** Every browser tool accepts `surfaceId` — each Claude Code session controls its own browser independently.
|
|
98
|
+
|
|
99
|
+
### 5. Security that actually matters
|
|
100
|
+
|
|
101
|
+
- Token authentication on all IPC pipes
|
|
102
|
+
- SSRF protection — blocks private IPs, `file://`, `javascript:` schemes
|
|
103
|
+
- PTY input sanitization — prevents command injection
|
|
104
|
+
- Randomized CDP port — no fixed debug port
|
|
105
|
+
- Memory pressure watchdog — reaps dead sessions at 750MB, blocks new ones at 1GB
|
|
106
|
+
- Electron Fuses — RunAsNode disabled, cookie encryption enabled
|
|
35
107
|
|
|
36
108
|
---
|
|
37
109
|
|
|
38
|
-
## Features
|
|
110
|
+
## All Features
|
|
39
111
|
|
|
40
112
|
### Terminal
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
- **Scrollback persistence** — terminal content saved to disk, restored on restart
|
|
113
|
+
- xterm.js + WebGL GPU-accelerated rendering
|
|
114
|
+
- ConPTY native Windows pseudo-terminal
|
|
115
|
+
- Split panes — `Ctrl+D` horizontal, `Ctrl+Shift+D` vertical
|
|
116
|
+
- Tabs — multiple surfaces per pane
|
|
117
|
+
- Vi copy mode — `Ctrl+Shift+X`
|
|
118
|
+
- Search — `Ctrl+F`
|
|
119
|
+
- 999K line scrollback with disk persistence
|
|
49
120
|
|
|
50
121
|
### Workspaces
|
|
51
122
|
- Sidebar with drag-and-drop reordering
|
|
52
|
-
- `Ctrl+1
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
-
|
|
123
|
+
- `Ctrl+1~9` quick switch
|
|
124
|
+
- Multiview — `Ctrl+click` to view multiple workspaces side by side
|
|
125
|
+
- Full session persistence — layout, tabs, cwd, scrollback all restored
|
|
126
|
+
- One-click reset in Settings
|
|
56
127
|
|
|
57
|
-
### Browser
|
|
128
|
+
### Browser + CDP Automation
|
|
58
129
|
- Built-in browser panel — `Ctrl+Shift+L`
|
|
59
130
|
- Navigation bar, DevTools, back/forward
|
|
60
|
-
-
|
|
61
|
-
|
|
62
|
-
```
|
|
63
|
-
[Inspector] Google (https://www.google.com/)
|
|
64
|
-
selector: input.gLFyf
|
|
65
|
-
<input type="text" name="q" aria-label="Search">
|
|
66
|
-
text: ""
|
|
67
|
-
parent: div.RNNXgb > siblings: button"Google Search", button"I'm Feeling Lucky"
|
|
68
|
-
```
|
|
69
|
-
- Paste directly into Claude — it understands the element immediately
|
|
131
|
+
- Element Inspector — hover to highlight, click to copy LLM-friendly context
|
|
132
|
+
- Full CDP automation: click, fill, type, screenshot, JS eval, key press
|
|
70
133
|
|
|
71
134
|
### Notifications
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
76
|
-
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
| `browser_click` | Click element by CSS selector |
|
|
88
|
-
| `browser_fill` | Fill input field |
|
|
89
|
-
| `browser_eval` | Execute JavaScript |
|
|
90
|
-
| `terminal_read` | Read terminal screen |
|
|
91
|
-
| `terminal_send` | Send text to terminal |
|
|
92
|
-
| `terminal_send_key` | Send key (enter, ctrl+c, etc.) |
|
|
93
|
-
| `workspace_list` | List all workspaces |
|
|
94
|
-
| `surface_list` | List surfaces |
|
|
95
|
-
| `pane_list` | List panes |
|
|
96
|
-
|
|
97
|
-
**Multi-agent:** All browser tools accept `surfaceId` — each Claude Code session controls its own browser independently.
|
|
98
|
-
|
|
99
|
-
### Agent Status Detection
|
|
100
|
-
Gate-based detection for AI coding agents:
|
|
101
|
-
- Claude Code, Cursor, Aider, Codex CLI, Gemini CLI, OpenCode, GitHub Copilot CLI
|
|
102
|
-
- Detects agent startup → activates monitoring
|
|
103
|
-
- Critical action warnings (git push --force, rm -rf, DROP TABLE, etc.)
|
|
135
|
+
- Output throughput-based activity detection
|
|
136
|
+
- Taskbar flash + Windows toast notifications
|
|
137
|
+
- Process exit alerts
|
|
138
|
+
- Notification panel — `Ctrl+I`
|
|
139
|
+
- Web Audio sound effects
|
|
140
|
+
|
|
141
|
+
### Agent Detection
|
|
142
|
+
Claude Code, Cursor, Aider, Codex CLI, Gemini CLI, OpenCode, GitHub Copilot CLI
|
|
143
|
+
- Detects agent start → activates monitoring
|
|
144
|
+
- Critical action warnings
|
|
145
|
+
|
|
146
|
+
### Daemon Process
|
|
147
|
+
- Background session management (survives app restart)
|
|
148
|
+
- Scrollback buffer dump and auto-recovery
|
|
149
|
+
- Dead session TTL reaping (24h default)
|
|
104
150
|
|
|
105
151
|
### Themes
|
|
106
152
|
Catppuccin, Tokyo Night, Dracula, Nord, Gruvbox, Solarized, One Dark, and more.
|
|
107
153
|
|
|
108
154
|
### i18n
|
|
109
|
-
English,
|
|
155
|
+
English, Korean, Japanese, Chinese
|
|
110
156
|
|
|
111
157
|
---
|
|
112
158
|
|
|
@@ -120,7 +166,7 @@ English, 한국어, 日本語, 中文
|
|
|
120
166
|
| `Ctrl+W` | Close tab |
|
|
121
167
|
| `Ctrl+N` | New workspace |
|
|
122
168
|
| `Ctrl+1~9` | Switch workspace |
|
|
123
|
-
| `Ctrl+click` | Add
|
|
169
|
+
| `Ctrl+click` | Add to multiview |
|
|
124
170
|
| `Ctrl+Shift+G` | Exit multiview |
|
|
125
171
|
| `Ctrl+Shift+L` | Open browser |
|
|
126
172
|
| `Ctrl+B` | Toggle sidebar |
|
|
@@ -129,8 +175,6 @@ English, 한국어, 日本語, 中文
|
|
|
129
175
|
| `Ctrl+,` | Settings |
|
|
130
176
|
| `Ctrl+F` | Search terminal |
|
|
131
177
|
| `Ctrl+Shift+X` | Vi copy mode |
|
|
132
|
-
| `Ctrl+Shift+H` | Flash pane |
|
|
133
|
-
| `Alt+Ctrl+Arrow` | Focus adjacent pane |
|
|
134
178
|
| `F12` | Browser DevTools |
|
|
135
179
|
|
|
136
180
|
---
|
|
@@ -159,7 +203,7 @@ npm start # Dev mode
|
|
|
159
203
|
npm run make # Build installer
|
|
160
204
|
```
|
|
161
205
|
|
|
162
|
-
### Requirements (
|
|
206
|
+
### Requirements (dev only)
|
|
163
207
|
- Node.js 18+
|
|
164
208
|
- Python 3.x (for node-gyp)
|
|
165
209
|
- Visual Studio Build Tools with C++ workload
|
|
@@ -176,37 +220,44 @@ Electron Main Process
|
|
|
176
220
|
├── PTYBridge (data forwarding + ActivityMonitor)
|
|
177
221
|
├── AgentDetector (gate-based agent status)
|
|
178
222
|
├── SessionManager (atomic save with .bak recovery)
|
|
179
|
-
├── ScrollbackPersistence (dump/load
|
|
180
|
-
├── PipeServer (Named Pipe JSON-RPC)
|
|
223
|
+
├── ScrollbackPersistence (terminal buffer dump/load)
|
|
224
|
+
├── PipeServer (Named Pipe JSON-RPC + token auth)
|
|
181
225
|
├── McpRegistrar (auto-registers MCP in ~/.claude.json)
|
|
182
|
-
├──
|
|
226
|
+
├── WebviewCdpManager (CDP proxy to <webview>)
|
|
227
|
+
├── DaemonClient (daemon mode connector)
|
|
183
228
|
└── ToastManager (OS notifications + taskbar flash)
|
|
184
229
|
|
|
185
230
|
Renderer Process (React 19 + Zustand)
|
|
186
231
|
├── PaneContainer (recursive split layout)
|
|
187
232
|
├── Terminal (xterm.js + WebGL + scrollback restore)
|
|
188
|
-
├── BrowserPanel (webview + Inspector)
|
|
233
|
+
├── BrowserPanel (webview + Inspector + CDP)
|
|
189
234
|
├── NotificationPanel
|
|
235
|
+
├── SettingsPanel (workspace reset)
|
|
190
236
|
└── Multiview grid
|
|
191
237
|
|
|
192
|
-
Daemon Process (
|
|
238
|
+
Daemon Process (standalone)
|
|
193
239
|
├── DaemonSessionManager (ConPTY lifecycle)
|
|
194
240
|
├── RingBuffer (circular scrollback buffer)
|
|
195
241
|
├── StateWriter (session suspend/resume)
|
|
196
|
-
|
|
242
|
+
├── ProcessMonitor (external process watchdog)
|
|
243
|
+
├── Watchdog (memory pressure escalation)
|
|
244
|
+
└── DaemonPipeServer (Named Pipe RPC + token auth)
|
|
197
245
|
|
|
198
246
|
MCP Server (stdio)
|
|
199
|
-
|
|
247
|
+
├── PlaywrightEngine (CDP connection, fast-fail)
|
|
248
|
+
├── CDP RPC fallback (screenshot, evaluate, type, click)
|
|
249
|
+
└── Claude Code <-> wmux Named Pipe RPC bridge
|
|
200
250
|
```
|
|
201
251
|
|
|
202
252
|
---
|
|
203
253
|
|
|
204
254
|
## Acknowledgments
|
|
205
255
|
|
|
206
|
-
- [cmux](https://github.com/manaflow-ai/cmux) — The macOS AI agent terminal that inspired wmux
|
|
256
|
+
- [cmux](https://github.com/manaflow-ai/cmux) — The macOS AI agent terminal that inspired wmux
|
|
207
257
|
- [xterm.js](https://xtermjs.org/) — Terminal rendering
|
|
208
258
|
- [node-pty](https://github.com/microsoft/node-pty) — Pseudo-terminal
|
|
209
259
|
- [Electron](https://www.electronjs.org/) — Desktop framework
|
|
260
|
+
- [Playwright](https://playwright.dev/) — Browser automation engine
|
|
210
261
|
|
|
211
262
|
---
|
|
212
263
|
|
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.handleSystem = handleSystem;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
4
6
|
const client_1 = require("../client");
|
|
5
7
|
const utils_1 = require("../utils");
|
|
8
|
+
function getFallbackVersion() {
|
|
9
|
+
try {
|
|
10
|
+
const pkg = JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(__dirname, '..', '..', 'package.json'), 'utf-8'));
|
|
11
|
+
return pkg.version ?? '0.0.0';
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return '0.0.0';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
6
17
|
async function handleSystem(cmd, args, jsonMode) {
|
|
7
18
|
let response;
|
|
8
19
|
switch (cmd) {
|
|
@@ -18,7 +29,7 @@ async function handleSystem(cmd, args, jsonMode) {
|
|
|
18
29
|
}
|
|
19
30
|
const info = response.result;
|
|
20
31
|
console.log(`app: ${info?.app ?? 'wmux'}`);
|
|
21
|
-
console.log(`version: ${info?.version ??
|
|
32
|
+
console.log(`version: ${info?.version ?? getFallbackVersion()}`);
|
|
22
33
|
console.log(`platform: ${info?.platform ?? process.platform}`);
|
|
23
34
|
}
|
|
24
35
|
break;
|
package/dist/cli/shared/rpc.js
CHANGED
|
@@ -34,6 +34,12 @@ exports.ALL_RPC_METHODS = [
|
|
|
34
34
|
'browser.type.humanlike',
|
|
35
35
|
'browser.cdp.target',
|
|
36
36
|
'browser.cdp.info',
|
|
37
|
+
'browser.cdp.send',
|
|
38
|
+
'browser.screenshot',
|
|
39
|
+
'browser.evaluate',
|
|
40
|
+
'browser.type.cdp',
|
|
41
|
+
'browser.click.cdp',
|
|
42
|
+
'browser.press.cdp',
|
|
37
43
|
'daemon.createSession',
|
|
38
44
|
'daemon.destroySession',
|
|
39
45
|
'daemon.attachSession',
|
package/dist/cli/shared/types.js
CHANGED
|
@@ -7,19 +7,22 @@ exports.validateMessage = validateMessage;
|
|
|
7
7
|
exports.createSurface = createSurface;
|
|
8
8
|
exports.createLeafPane = createLeafPane;
|
|
9
9
|
exports.createWorkspace = createWorkspace;
|
|
10
|
+
exports.validateNavigationUrl = validateNavigationUrl;
|
|
10
11
|
// === Utility: generate unique IDs ===
|
|
11
12
|
function generateId(prefix) {
|
|
12
13
|
return `${prefix}-${crypto.randomUUID()}`;
|
|
13
14
|
}
|
|
14
15
|
// === Security: sanitize text before PTY write ===
|
|
15
16
|
/**
|
|
16
|
-
* Strips control characters
|
|
17
|
-
*
|
|
17
|
+
* Strips dangerous control characters from text before writing to a PTY.
|
|
18
|
+
* Removes: NULL byte (\x00) and C1 control characters (\x80-\x9f).
|
|
19
|
+
* Preserves: CR (\r), LF (\n), Tab (\t), ESC sequences (\x1b[...),
|
|
20
|
+
* and other standard terminal control characters needed for normal operation.
|
|
18
21
|
*/
|
|
19
22
|
function sanitizePtyText(text) {
|
|
20
|
-
// Remove
|
|
23
|
+
// Remove NULL byte and C1 control characters (U+0080–U+009F)
|
|
21
24
|
// eslint-disable-next-line no-control-regex
|
|
22
|
-
return text.replace(/[\x00
|
|
25
|
+
return text.replace(/[\x00\u0080-\u009f]/g, '');
|
|
23
26
|
}
|
|
24
27
|
/**
|
|
25
28
|
* Validates and clamps a user-supplied name string.
|
|
@@ -77,3 +80,104 @@ function createWorkspace(name) {
|
|
|
77
80
|
activePaneId: rootPane.id,
|
|
78
81
|
};
|
|
79
82
|
}
|
|
83
|
+
// === Security: URL validation for SSRF prevention ===
|
|
84
|
+
/**
|
|
85
|
+
* Validates a URL for safe navigation. Blocks dangerous schemes and private
|
|
86
|
+
* network addresses to prevent SSRF attacks from AI agent-driven browsing.
|
|
87
|
+
*
|
|
88
|
+
* Allows localhost/127.0.0.1/[::1] for local development servers.
|
|
89
|
+
*
|
|
90
|
+
* NOTE (v1 limitation): This is string-based validation only. DNS-resolved IPs
|
|
91
|
+
* are not checked, so DNS rebinding attacks are not mitigated. A future version
|
|
92
|
+
* should resolve hostnames and re-validate the resolved IP.
|
|
93
|
+
*/
|
|
94
|
+
function validateNavigationUrl(url) {
|
|
95
|
+
let parsed;
|
|
96
|
+
try {
|
|
97
|
+
parsed = new URL(url);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return { valid: false, reason: 'Invalid URL' };
|
|
101
|
+
}
|
|
102
|
+
// Only allow http and https schemes
|
|
103
|
+
const scheme = parsed.protocol.toLowerCase();
|
|
104
|
+
if (scheme !== 'http:' && scheme !== 'https:') {
|
|
105
|
+
return { valid: false, reason: `Blocked URL scheme: ${scheme}` };
|
|
106
|
+
}
|
|
107
|
+
// Extract hostname (strip brackets from IPv6)
|
|
108
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
109
|
+
// Allow localhost and IPv4/IPv6 loopback
|
|
110
|
+
if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1') {
|
|
111
|
+
return { valid: true };
|
|
112
|
+
}
|
|
113
|
+
// Block IPv6 private/link-local ranges
|
|
114
|
+
if (hostname.startsWith('[') || hostname.includes(':')) {
|
|
115
|
+
// Hostname is an IPv6 address (URL parser strips brackets in .hostname)
|
|
116
|
+
const addr = hostname;
|
|
117
|
+
// Block fc00::/7 (unique local) — starts with fc or fd
|
|
118
|
+
if (addr.startsWith('fc') || addr.startsWith('fd')) {
|
|
119
|
+
return { valid: false, reason: 'Blocked private IPv6 address (fc00::/7)' };
|
|
120
|
+
}
|
|
121
|
+
// Block fe80::/10 (link-local) — starts with fe8, fe9, fea, feb
|
|
122
|
+
if (/^fe[89ab]/.test(addr)) {
|
|
123
|
+
return { valid: false, reason: 'Blocked link-local IPv6 address (fe80::/10)' };
|
|
124
|
+
}
|
|
125
|
+
// ::1 already allowed above; block any other loopback representation
|
|
126
|
+
// Normalize: collapse :: and check
|
|
127
|
+
if (addr === '0:0:0:0:0:0:0:1' || addr === '0000:0000:0000:0000:0000:0000:0000:0001') {
|
|
128
|
+
return { valid: true };
|
|
129
|
+
}
|
|
130
|
+
// Block null IPv6 address (:: or 0:0:0:0:0:0:0:0) — equivalent to 0.0.0.0
|
|
131
|
+
if (addr === '::' || addr === '0:0:0:0:0:0:0:0' || addr === '0000:0000:0000:0000:0000:0000:0000:0000') {
|
|
132
|
+
return { valid: false, reason: 'Blocked null IPv6 address (equivalent to 0.0.0.0)' };
|
|
133
|
+
}
|
|
134
|
+
// Block IPv4-mapped IPv6 (::ffff:x.x.x.x) and IPv4-compatible IPv6 (::x.x.x.x)
|
|
135
|
+
// These resolve to their embedded IPv4 address, bypassing IPv4 private IP checks.
|
|
136
|
+
const v4MappedMatch = /^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.exec(addr);
|
|
137
|
+
const v4CompatMatch = !v4MappedMatch ? /^::(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.exec(addr) : null;
|
|
138
|
+
const embeddedV4 = v4MappedMatch?.[1] ?? v4CompatMatch?.[1];
|
|
139
|
+
if (embeddedV4) {
|
|
140
|
+
// Recursively validate the embedded IPv4 through the same checks
|
|
141
|
+
const embeddedResult = validateNavigationUrl(`http://${embeddedV4}/`);
|
|
142
|
+
if (!embeddedResult.valid) {
|
|
143
|
+
return { valid: false, reason: `Blocked IPv4-mapped/compatible IPv6: embedded ${embeddedV4} — ${embeddedResult.reason}` };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return { valid: true };
|
|
147
|
+
}
|
|
148
|
+
// Check for IPv4 addresses
|
|
149
|
+
const ipv4Match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
|
|
150
|
+
if (ipv4Match) {
|
|
151
|
+
const octets = [
|
|
152
|
+
parseInt(ipv4Match[1], 10),
|
|
153
|
+
parseInt(ipv4Match[2], 10),
|
|
154
|
+
parseInt(ipv4Match[3], 10),
|
|
155
|
+
parseInt(ipv4Match[4], 10),
|
|
156
|
+
];
|
|
157
|
+
// 127.0.0.1 already allowed above; block other 127.x.x.x
|
|
158
|
+
if (octets[0] === 127) {
|
|
159
|
+
return { valid: false, reason: 'Blocked loopback address' };
|
|
160
|
+
}
|
|
161
|
+
// Block 10.0.0.0/8
|
|
162
|
+
if (octets[0] === 10) {
|
|
163
|
+
return { valid: false, reason: 'Blocked private IP address (10.0.0.0/8)' };
|
|
164
|
+
}
|
|
165
|
+
// Block 172.16.0.0/12 (172.16.x.x – 172.31.x.x)
|
|
166
|
+
if (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31) {
|
|
167
|
+
return { valid: false, reason: 'Blocked private IP address (172.16.0.0/12)' };
|
|
168
|
+
}
|
|
169
|
+
// Block 192.168.0.0/16
|
|
170
|
+
if (octets[0] === 192 && octets[1] === 168) {
|
|
171
|
+
return { valid: false, reason: 'Blocked private IP address (192.168.0.0/16)' };
|
|
172
|
+
}
|
|
173
|
+
// Block 169.254.0.0/16 (link-local, includes cloud metadata 169.254.169.254)
|
|
174
|
+
if (octets[0] === 169 && octets[1] === 254) {
|
|
175
|
+
return { valid: false, reason: 'Blocked link-local/cloud metadata address (169.254.0.0/16)' };
|
|
176
|
+
}
|
|
177
|
+
// Block 0.0.0.0
|
|
178
|
+
if (octets.every((o) => o === 0)) {
|
|
179
|
+
return { valid: false, reason: 'Blocked null address (0.0.0.0)' };
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return { valid: true };
|
|
183
|
+
}
|
package/dist/mcp/mcp/index.js
CHANGED
|
@@ -14,9 +14,20 @@ const wait_1 = require("./playwright/tools/wait");
|
|
|
14
14
|
const file_1 = require("./playwright/tools/file");
|
|
15
15
|
const utility_1 = require("./playwright/tools/utility");
|
|
16
16
|
const extraction_1 = require("./playwright/tools/extraction");
|
|
17
|
+
const fs_1 = require("fs");
|
|
18
|
+
const path_1 = require("path");
|
|
19
|
+
function getVersion() {
|
|
20
|
+
try {
|
|
21
|
+
const pkg = JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(__dirname, '..', '..', 'package.json'), 'utf-8'));
|
|
22
|
+
return pkg.version ?? '0.0.0';
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return '0.0.0';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
17
28
|
const server = new mcp_js_1.McpServer({
|
|
18
29
|
name: 'wmux',
|
|
19
|
-
version:
|
|
30
|
+
version: getVersion(),
|
|
20
31
|
});
|
|
21
32
|
// Helper: wrap an RPC call as an MCP tool result
|
|
22
33
|
async function callRpc(method, params = {}) {
|