starmux 0.11.0 → 0.13.0
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/CONTRIBUTING.md +1 -1
- package/README.md +169 -97
- package/SECURITY.md +3 -3
- package/dist/src/access-control.js +23 -20
- package/dist/src/access-control.js.map +1 -1
- package/dist/src/access-dashboard.js +8 -2
- package/dist/src/access-dashboard.js.map +1 -1
- package/dist/src/cli/instances.js +87 -14
- package/dist/src/cli/instances.js.map +1 -1
- package/dist/src/cli/mac-mcp-gateway.js +4 -3
- package/dist/src/cli/mac-mcp-gateway.js.map +1 -1
- package/dist/src/cli/starmux-status.js +122 -0
- package/dist/src/cli/starmux-status.js.map +1 -0
- package/dist/src/cli/starmux.js +222 -49
- package/dist/src/cli/starmux.js.map +1 -1
- package/dist/src/deny-dashboard.js +2 -2
- package/dist/src/deny-dashboard.js.map +1 -1
- package/dist/src/instance-runtime.js +11 -2
- package/dist/src/instance-runtime.js.map +1 -1
- package/dist/src/instance-store.js +3 -1
- package/dist/src/instance-store.js.map +1 -1
- package/dist/src/ops-dashboard.js +4 -4
- package/dist/src/ops-dashboard.js.map +1 -1
- package/dist/src/permissions-dashboard.js +2 -2
- package/dist/src/permissions-dashboard.js.map +1 -1
- package/dist/src/runtime-paths.js +3 -3
- package/dist/src/runtime-paths.js.map +1 -1
- package/dist/src/starmux-migration.js +1 -1
- package/dist/src/starmux-migration.js.map +1 -1
- package/dist/vendor/desktop-commander/LICENSE +21 -0
- package/dist/vendor/desktop-commander/dist/bootstrap.js +22 -0
- package/dist/vendor/desktop-commander/dist/command-manager.js +246 -0
- package/dist/vendor/desktop-commander/dist/config-field-definitions.js +37 -0
- package/dist/vendor/desktop-commander/dist/config-manager.js +634 -0
- package/dist/vendor/desktop-commander/dist/config-recovery.js +81 -0
- package/dist/vendor/desktop-commander/dist/config.js +19 -0
- package/dist/vendor/desktop-commander/dist/custom-stdio.js +358 -0
- package/dist/vendor/desktop-commander/dist/error-handlers.js +15 -0
- package/dist/vendor/desktop-commander/dist/handlers/edit-search-handlers.js +6 -0
- package/dist/vendor/desktop-commander/dist/handlers/filesystem-handlers.js +450 -0
- package/dist/vendor/desktop-commander/dist/handlers/history-handlers.js +67 -0
- package/dist/vendor/desktop-commander/dist/handlers/index.js +7 -0
- package/dist/vendor/desktop-commander/dist/handlers/process-handlers.js +15 -0
- package/dist/vendor/desktop-commander/dist/handlers/search-answers.js +172 -0
- package/dist/vendor/desktop-commander/dist/handlers/search-handlers.js +136 -0
- package/dist/vendor/desktop-commander/dist/handlers/terminal-handlers.js +35 -0
- package/dist/vendor/desktop-commander/dist/index.js +88 -0
- package/dist/vendor/desktop-commander/dist/search-manager.js +1262 -0
- package/dist/vendor/desktop-commander/dist/server.js +1414 -0
- package/dist/vendor/desktop-commander/dist/terminal-manager.js +757 -0
- package/dist/vendor/desktop-commander/dist/tools/config.js +211 -0
- package/dist/vendor/desktop-commander/dist/tools/edit.js +455 -0
- package/dist/vendor/desktop-commander/dist/tools/feedback.js +10 -0
- package/dist/vendor/desktop-commander/dist/tools/filesystem.js +1047 -0
- package/dist/vendor/desktop-commander/dist/tools/fuzzySearch.js +74 -0
- package/dist/vendor/desktop-commander/dist/tools/fuzzySearchCore.js +125 -0
- package/dist/vendor/desktop-commander/dist/tools/improved-process-tools.js +726 -0
- package/dist/vendor/desktop-commander/dist/tools/mime-types.js +28 -0
- package/dist/vendor/desktop-commander/dist/tools/pdf/extract-images.js +131 -0
- package/dist/vendor/desktop-commander/dist/tools/pdf/index.js +4 -0
- package/dist/vendor/desktop-commander/dist/tools/pdf/lib/pdf2md.js +78 -0
- package/dist/vendor/desktop-commander/dist/tools/pdf/manipulations.js +112 -0
- package/dist/vendor/desktop-commander/dist/tools/pdf/markdown.js +30 -0
- package/dist/vendor/desktop-commander/dist/tools/pdf/utils.js +45 -0
- package/dist/vendor/desktop-commander/dist/tools/process.js +90 -0
- package/dist/vendor/desktop-commander/dist/tools/prompts.js +259 -0
- package/dist/vendor/desktop-commander/dist/tools/schemas.js +242 -0
- package/dist/vendor/desktop-commander/dist/tools/usage.js +24 -0
- package/dist/vendor/desktop-commander/dist/types.js +1 -0
- package/dist/vendor/desktop-commander/dist/ui/config-editor/config-editor-runtime.js +150 -0
- package/dist/vendor/desktop-commander/dist/ui/config-editor/index.html +13 -0
- package/dist/vendor/desktop-commander/dist/ui/config-editor/styles.css +587 -0
- package/dist/vendor/desktop-commander/dist/ui/contracts.js +57 -0
- package/dist/vendor/desktop-commander/dist/ui/file-preview/index.html +16 -0
- package/dist/vendor/desktop-commander/dist/ui/file-preview/preview-runtime.js +471 -0
- package/dist/vendor/desktop-commander/dist/ui/file-preview/shared/preview-file-types.js +59 -0
- package/dist/vendor/desktop-commander/dist/ui/file-preview/styles.css +1631 -0
- package/dist/vendor/desktop-commander/dist/ui/resources.js +93 -0
- package/dist/vendor/desktop-commander/dist/utils/ab-test.js +122 -0
- package/dist/vendor/desktop-commander/dist/utils/atomic-write.js +80 -0
- package/dist/vendor/desktop-commander/dist/utils/capture.js +40 -0
- package/dist/vendor/desktop-commander/dist/utils/dockerPrompt.js +9 -0
- package/dist/vendor/desktop-commander/dist/utils/exit-process.js +31 -0
- package/dist/vendor/desktop-commander/dist/utils/feature-flags.js +18 -0
- package/dist/vendor/desktop-commander/dist/utils/files/base.js +5 -0
- package/dist/vendor/desktop-commander/dist/utils/files/binary.js +65 -0
- package/dist/vendor/desktop-commander/dist/utils/files/docx.js +694 -0
- package/dist/vendor/desktop-commander/dist/utils/files/excel.js +497 -0
- package/dist/vendor/desktop-commander/dist/utils/files/factory.js +168 -0
- package/dist/vendor/desktop-commander/dist/utils/files/image.js +89 -0
- package/dist/vendor/desktop-commander/dist/utils/files/index.js +14 -0
- package/dist/vendor/desktop-commander/dist/utils/files/pdf.js +124 -0
- package/dist/vendor/desktop-commander/dist/utils/files/text.js +423 -0
- package/dist/vendor/desktop-commander/dist/utils/fuzzySearchLogger.js +126 -0
- package/dist/vendor/desktop-commander/dist/utils/internal-facts.js +23 -0
- package/dist/vendor/desktop-commander/dist/utils/lazy-package.js +44 -0
- package/dist/vendor/desktop-commander/dist/utils/lineEndingHandler.js +77 -0
- package/dist/vendor/desktop-commander/dist/utils/logger.js +72 -0
- package/dist/vendor/desktop-commander/dist/utils/mcp-ui-ab-test.js +62 -0
- package/dist/vendor/desktop-commander/dist/utils/open-browser.js +2 -0
- package/dist/vendor/desktop-commander/dist/utils/process-detection.js +260 -0
- package/dist/vendor/desktop-commander/dist/utils/process-tree.js +223 -0
- package/dist/vendor/desktop-commander/dist/utils/rename.js +74 -0
- package/dist/vendor/desktop-commander/dist/utils/ripgrep-resolver.js +69 -0
- package/dist/vendor/desktop-commander/dist/utils/shell.js +187 -0
- package/dist/vendor/desktop-commander/dist/utils/system-info.js +727 -0
- package/dist/vendor/desktop-commander/dist/utils/terminal-timestamps.js +18 -0
- package/dist/vendor/desktop-commander/dist/utils/toolHistory.js +309 -0
- package/dist/vendor/desktop-commander/dist/utils/trackTools.js +55 -0
- package/dist/vendor/desktop-commander/dist/utils/unsupportedParams.js +66 -0
- package/dist/vendor/desktop-commander/dist/utils/usageTracker.js +225 -0
- package/dist/vendor/desktop-commander/dist/utils/welcome-onboarding.js +2 -0
- package/dist/vendor/desktop-commander/dist/utils/withTimeout.js +85 -0
- package/dist/vendor/desktop-commander/dist/version.js +1 -0
- package/dist/vendor/desktop-commander/package.json +15 -0
- package/docs/ACCESS_CONTROL.md +5 -5
- package/docs/CLIENTS.md +11 -19
- package/docs/CONNECT_CHATGPT.md +4 -6
- package/docs/INSTALL.md +9 -5
- package/docs/INSTANCES.md +5 -2
- package/docs/OAUTH.md +6 -14
- package/docs/PLATFORMS.md +4 -4
- package/docs/README.md +2 -11
- package/docs/REMOTE_SETUP.md +13 -13
- package/docs/SECURITY.md +5 -5
- package/docs/assets/starmux-architecture.svg +315 -0
- package/package.json +14 -2
- package/docs/ARCHITECTURE.md +0 -51
- package/docs/MIGRATION.md +0 -37
- package/docs/RELEASE_STATUS.md +0 -31
- package/docs/TYPESCRIPT_MIGRATION.md +0 -56
- package/docs/VALIDATION_OAUTH.md +0 -79
- package/docs/VALIDATION_TUNNEL.md +0 -39
- package/docs/VALIDATION_WORKBUDDY.md +0 -59
- package/docs/assets/mcp-connection-route.svg +0 -104
- package/docs/assets/mcp-gateway-simple.svg +0 -62
package/CONTRIBUTING.md
CHANGED
|
@@ -7,7 +7,7 @@ Thank you for helping build an independent, self-hosted MCP Gateway.
|
|
|
7
7
|
- Fixes must preserve upstream Desktop Commander tool schemas and accurately separate local health from remote caller identity.
|
|
8
8
|
- Feature ideas for programmable hooks, broader multi-upstream routing or multi-computer operation are welcome, but should be labeled **proposals** until independently implemented and tested.
|
|
9
9
|
|
|
10
|
-
See [security](SECURITY.md) and [
|
|
10
|
+
See [security](SECURITY.md) and [platform coverage](docs/PLATFORMS.md).
|
|
11
11
|
|
|
12
12
|
## TypeScript development
|
|
13
13
|
|
package/README.md
CHANGED
|
@@ -1,177 +1,249 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
# Starmux
|
|
3
|
+
# Starmux MCP Gateway
|
|
4
4
|
|
|
5
|
-
###
|
|
5
|
+
### Give cloud AI clients secure access to your local machine.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**Self-hosted MCP Gateway · Built-in File, Shell & Git Tools · macOS Menu Bar App · MIT**
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
[Quick Start](#quick-start) · [Connect a client](docs/REMOTE_SETUP.md) · [Documentation](docs/README.md)
|
|
9
|
+
[Why Starmux?](#why-starmux) · [Quick Start](#quick-start) · [Connecting AI Clients](#connecting-ai-clients) · [macOS App](#native-macos-app) · [Documentation](docs/README.md)
|
|
12
10
|
|
|
13
11
|
</div>
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Why Starmux?
|
|
16
|
+
|
|
17
|
+
Cloud AI assistants (like **ChatGPT Web**, **Claude**, **Codex**, or **WorkBuddy**) are powerful, but they are trapped in the cloud—isolated from your local codebases, terminal, and development environment.
|
|
18
|
+
|
|
19
|
+
**Starmux** (`Star` topology + `Multiplexer`) is a self-hosted MCP gateway that connects remote AI clients to the real tools on your own computers over Direct HTTPS or a Secure Tunnel:
|
|
16
20
|
|
|
17
|
-
|
|
21
|
+
<div align="center">
|
|
22
|
+
<img src="docs/assets/starmux-architecture.svg" width="100%" alt="Starmux architecture: Many AI clients connecting to self-hosted computers via Direct HTTPS or Secure MCP Tunnel" />
|
|
23
|
+
</div>
|
|
18
24
|
|
|
19
|
-
- **
|
|
20
|
-
- **
|
|
21
|
-
- **
|
|
25
|
+
- **Batteries-Included Workspace Tools:** Powered by a built-in [Desktop Commander](https://github.com/wonderwhy-er/DesktopCommanderMCP) engine (installed automatically via npm). Out of the box, your AI can read/write files, search code, run Shell commands, and manage Git.
|
|
26
|
+
- **Code on Any Host (macOS, Linux, Windows & Edge Devices):** Many AI clients (like **ChatGPT Desktop** or **WorkBuddy**) have no native Linux or ARM64 client. Because Starmux runs anywhere **Node.js 22.12+** runs—including **macOS**, **Linux (`x64` / `arm64`)**, **NVIDIA Jetson**, headless servers, and **Windows**—you can use your favorite AI client on your main computer or browser to develop directly on any target machine.
|
|
27
|
+
- **Safe by Default:** Starts in strict **Read-Only** mode (`starmux up`). Explicitly opt into **Coding Mode** (`starmux up --code`) only when you want AI to edit files or run terminal commands.
|
|
28
|
+
- **Your Network, Your Keys:** No vendor-hosted relay required. Protect your gateway with Bearer **API Keys**, browser-based **OAuth**, or your existing **Tunnel / Reverse Proxy** access controls. Revoke access instantly at any time.
|
|
22
29
|
|
|
23
|
-
|
|
30
|
+
---
|
|
24
31
|
|
|
25
32
|
## Quick Start
|
|
26
33
|
|
|
27
34
|
### 1. Install
|
|
28
35
|
|
|
29
|
-
Requires Node.js
|
|
36
|
+
Requires **Node.js 22.12+**.
|
|
30
37
|
|
|
31
38
|
```bash
|
|
32
39
|
npm install -g starmux
|
|
33
40
|
```
|
|
34
41
|
|
|
35
|
-
### 2. Start in your project
|
|
42
|
+
### 2. Start in your project directory
|
|
36
43
|
|
|
37
44
|
```bash
|
|
38
45
|
cd /path/to/your/project
|
|
39
46
|
|
|
40
|
-
# Safe read-only mode (default)
|
|
47
|
+
# Option A: Safe read-only mode (default — inspect files & code)
|
|
41
48
|
starmux up
|
|
42
49
|
|
|
43
|
-
# Full coding mode
|
|
50
|
+
# Option B: Full coding mode (edit files, run Shell & Git)
|
|
44
51
|
starmux up --code
|
|
45
52
|
```
|
|
46
53
|
|
|
47
|
-
Starmux initializes itself and runs in the background
|
|
54
|
+
Starmux initializes itself and runs in the background on port `18893` (coding mode asks for confirmation the first time):
|
|
48
55
|
|
|
49
56
|
```text
|
|
50
|
-
Starmux
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
● Starmux is running
|
|
58
|
+
Listen http://127.0.0.1:18893/mcp (this computer only)
|
|
59
|
+
Workspace /path/to/your/project
|
|
60
|
+
Mode Read-only (run 'starmux up --code' to enable edits & shell)
|
|
61
|
+
Auth None (no token required)
|
|
62
|
+
Runtime Background · continues after this terminal closes
|
|
63
|
+
|
|
64
|
+
Connect an AI client (MCP settings)
|
|
65
|
+
Transport Streamable HTTP
|
|
66
|
+
URL (Local) http://127.0.0.1:18893/mcp (AI client on this computer)
|
|
67
|
+
URL (Remote) https://<your-domain-or-tunnel>/mcp (via protected HTTPS proxy or tunnel -> 127.0.0.1:18893)
|
|
68
|
+
Authentication None (optional Bearer API key: starmux auth key "My AI Client")
|
|
69
|
+
|
|
70
|
+
Commands
|
|
71
|
+
Verify starmux verify
|
|
72
|
+
Auth & keys starmux auth
|
|
73
|
+
Dashboard starmux dashboard (view & copy settings in browser)
|
|
74
|
+
Logs starmux logs -f
|
|
75
|
+
Stop starmux down
|
|
57
76
|
```
|
|
58
77
|
|
|
59
|
-
### 3.
|
|
78
|
+
### 3. Add Starmux to your AI client
|
|
60
79
|
|
|
61
|
-
|
|
80
|
+
Need a Bearer API key for remote access? Generate one first (optional for local use):
|
|
62
81
|
|
|
63
82
|
```bash
|
|
64
83
|
starmux auth key "My AI Client"
|
|
65
84
|
```
|
|
66
85
|
|
|
67
|
-
|
|
86
|
+
Depending on your AI client, configure the MCP server using either **Form UI** or **JSON**:
|
|
68
87
|
|
|
69
|
-
|
|
88
|
+
#### Option A — AI clients with a Form UI (e.g., ChatGPT Web, Claude)
|
|
70
89
|
|
|
71
|
-
|
|
90
|
+
Copy the fields directly from `starmux up` (or open `starmux dashboard` in your browser):
|
|
72
91
|
|
|
92
|
+
| MCP Client Field | Value to Fill In |
|
|
93
|
+
| :--- | :--- |
|
|
94
|
+
| **Transport** | `Streamable HTTP` |
|
|
95
|
+
| **URL (Local)** | `http://127.0.0.1:18893/mcp` *(when the AI client is on this same computer)* |
|
|
96
|
+
| **URL (Remote)** | `https://<your-domain-or-tunnel>/mcp` *(forwarded to `127.0.0.1:18893` via Caddy or Tunnel)* |
|
|
97
|
+
| **Authentication** | *Optional:* `None` *(default)*, `Bearer API key` (`Authorization: Bearer <YOUR_API_KEY>`), or `OAuth` |
|
|
73
98
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
99
|
+
#### Option B — AI clients with JSON config (e.g., WorkBuddy, Cursor, Windsurf)
|
|
100
|
+
|
|
101
|
+
Paste one of the blocks below into your client's `mcp.json` / `mcpServers` settings.
|
|
102
|
+
|
|
103
|
+
**Without API key (default for local `127.0.0.1` or when your tunnel handles login — no `headers` needed):**
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"mcpServers": {
|
|
108
|
+
"starmux": {
|
|
109
|
+
"type": "streamable-http",
|
|
110
|
+
"url": "http://127.0.0.1:18893/mcp"
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
80
114
|
```
|
|
81
115
|
|
|
82
|
-
|
|
116
|
+
**With Bearer API key (`headers` is optional — include it after running `starmux auth key`):**
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"mcpServers": {
|
|
121
|
+
"starmux": {
|
|
122
|
+
"type": "streamable-http",
|
|
123
|
+
"url": "https://<your-domain-or-tunnel>/mcp",
|
|
124
|
+
"headers": {
|
|
125
|
+
"Authorization": "Bearer smx_YOUR_GENERATED_API_KEY"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
83
131
|
|
|
132
|
+
---
|
|
84
133
|
|
|
85
|
-
|
|
86
|
-
| --- | --- |
|
|
87
|
-
| WorkBuddy or a client accepting Bearer headers | [HTTPS + API key](docs/REMOTE_SETUP.md#2-https--bearer-api-key) |
|
|
88
|
-
| ChatGPT with browser login | [OAuth provider and connection setup](docs/OAUTH.md) |
|
|
89
|
-
| An existing protected ingress or MCP Tunnel | [Remote connection guide](docs/REMOTE_SETUP.md) |
|
|
134
|
+
## Connecting AI Clients
|
|
90
135
|
|
|
91
|
-
|
|
136
|
+
By design, Starmux only listens on `127.0.0.1:18893/mcp` (loopback) and never exposes raw ports directly to external networks:
|
|
92
137
|
|
|
93
|
-
|
|
138
|
+
- **Local AI Client (Same Computer):** Connects directly to `http://127.0.0.1:18893/mcp` with zero network configuration.
|
|
139
|
+
- **Remote / Cloud AI Client:** Connects through an **HTTPS Reverse Proxy** or a **Tunnel Client** running on your machine, which receives external HTTPS traffic and forwards it locally to `127.0.0.1:18893/mcp`:
|
|
94
140
|
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
starmux logs -f
|
|
101
|
-
starmux down # Alias: stop
|
|
141
|
+
```text
|
|
142
|
+
Remote AI Client
|
|
143
|
+
──(HTTPS)──▶ HTTPS Reverse Proxy (Caddy) or Tunnel Client
|
|
144
|
+
──(Loopback)──▶ Starmux (127.0.0.1:18893/mcp)
|
|
145
|
+
──▶ Local Files, Shell & Git
|
|
102
146
|
```
|
|
103
147
|
|
|
104
|
-
|
|
148
|
+
### 1. Choose a Network Forwarder (for Remote / Cloud Access)
|
|
105
149
|
|
|
106
|
-
|
|
150
|
+
| Network Method | Best For | Recommended Software & Official Docs | Starmux Guide |
|
|
151
|
+
| :--- | :--- | :--- | :--- |
|
|
152
|
+
| **Direct HTTPS (Reverse Proxy)** | Hosts with a public IPv4 / IPv6 address and a domain name | [Caddy](https://caddyserver.com/docs/quick-starts/reverse-proxy) *(auto HTTPS)* · [Nginx](https://nginx.org/en/docs/http/ngx_http_proxy_module.html) | [Caddy HTTPS Setup](docs/REMOTE_SETUP.md#2-https--bearer-api-key) |
|
|
153
|
+
| **Secure Tunnel** | Hosts behind NAT, home Wi-Fi, or office firewalls *(no inbound port needed)* | [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) · [Tailscale Funnel / Serve](https://tailscale.com/kb/1223/funnel) · [OpenAI Secure MCP Tunnel](https://developers.openai.com/api/docs/guides/secure-mcp-tunnels) · [ngrok](https://ngrok.com/docs) | [Tunnel Setup](docs/CONNECT_CHATGPT.md#2-secure-mcp-tunnel--no-inbound-port) |
|
|
107
154
|
|
|
108
|
-
|
|
155
|
+
### 2. Choose an Authentication Mode
|
|
109
156
|
|
|
110
|
-
|
|
111
|
-
<img src="docs/assets/starmux-app-en.png" width="430" alt="Starmux menu bar showing service status, controls, connection topology and recent tool activity" />
|
|
112
|
-
<p><sub>Native macOS menu bar App · English and Chinese · Development build</sub></p>
|
|
113
|
-
</div>
|
|
157
|
+
Authentication is configured independently of how you expose the network:
|
|
114
158
|
|
|
115
|
-
|
|
159
|
+
| Authentication Mode | When to Use | CLI Command |
|
|
160
|
+
| :--- | :--- | :--- |
|
|
161
|
+
| **None** *(default)* | Same-computer clients, or tunnels that already handle caller login | `starmux auth mode none` |
|
|
162
|
+
| **Bearer API Key** *(recommended)* | Clients that support an `Authorization: Bearer <key>` header | `starmux auth key "My AI Client"` |
|
|
163
|
+
| **OAuth 2.0** | Cloud clients that require browser sign-in (e.g., ChatGPT Web) | `starmux auth oauth setup ...` |
|
|
116
164
|
|
|
117
|
-
|
|
165
|
+
### 3. Client Setup Guides
|
|
118
166
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
167
|
+
| AI Client | Recommended Connection & Auth | Status | Setup Guide |
|
|
168
|
+
| :--- | :--- | :--- | :--- |
|
|
169
|
+
| **ChatGPT Web** | Secure Tunnel + OAuth (Browser Login) | ✅ Tested | [ChatGPT OAuth Guide](docs/OAUTH.md) |
|
|
170
|
+
| **Claude App** | Remote MCP Custom Connector | 🎯 Target | [Client Compatibility](docs/CLIENTS.md) |
|
|
171
|
+
| **Codex** | Protected Tunnel (No Starmux Token) | ✅ Tested | [Remote Setup Guide](docs/REMOTE_SETUP.md) |
|
|
172
|
+
| **WorkBuddy** | Direct HTTPS + Bearer API Key | ✅ Tested | [HTTPS + API Key Guide](docs/REMOTE_SETUP.md#2-https--bearer-api-key) |
|
|
173
|
+
| **Doubao (豆包)** | HTTPS / Tunnel | ✅ Community Verified | [Client Compatibility](docs/CLIENTS.md) |
|
|
174
|
+
|
|
175
|
+
See [Client Compatibility](docs/CLIENTS.md) and [Remote Setup](docs/REMOTE_SETUP.md) for DNS, TLS, Caddy, OAuth, and tunnel configuration details.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Everyday CLI & Multi-Workspace
|
|
180
|
+
|
|
181
|
+
Manage your gateway with simple, human-friendly commands:
|
|
124
182
|
|
|
125
|
-
|
|
126
|
-
starmux
|
|
183
|
+
```bash
|
|
184
|
+
starmux status # Check health, endpoints, and active permission mode
|
|
185
|
+
starmux auth # View auth mode, active API keys, and OAuth sessions
|
|
186
|
+
starmux verify # Test local MCP handshake, tool discovery, and workspace read
|
|
187
|
+
starmux doctor # Run environment and configuration diagnostics
|
|
188
|
+
starmux dashboard # Open the local Web Access Console to copy settings
|
|
189
|
+
starmux logs -f # Stream live gateway logs
|
|
190
|
+
starmux down # Stop the gateway (alias: stop)
|
|
127
191
|
```
|
|
128
192
|
|
|
129
|
-
|
|
193
|
+
After initialization, `starmux up` reuses your saved workspace. Use `starmux up .` to switch it to your current directory, or pass `--foreground` to keep the process in your terminal.
|
|
130
194
|
|
|
131
|
-
|
|
195
|
+
### Advanced: Running multiple workspaces (`-i`)
|
|
132
196
|
|
|
133
|
-
|
|
197
|
+
*(Optional)* By default, Starmux manages a single `default` workspace so you never have to pass extra flags. When you need to expose multiple projects simultaneously or run different permission modes on the same machine, pass `-i <name>` to create isolated instances with their own ports, credentials, policies, and logs:
|
|
134
198
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
199
|
+
```bash
|
|
200
|
+
starmux up ~/work/repo-a -i work --code
|
|
201
|
+
starmux up ~/personal/blog -i blog --read
|
|
202
|
+
starmux ls # List all instances with status, mode, port, and workspace
|
|
203
|
+
starmux down -i work # Stop a specific instance
|
|
204
|
+
starmux rm blog # Stop and archive an instance
|
|
205
|
+
```
|
|
142
206
|
|
|
143
|
-
[
|
|
207
|
+
See [Instances and Configuration](docs/INSTANCES.md) for advanced JSONC configuration (`starmux config edit` / `starmux config apply`) and hot-reload workflows.
|
|
144
208
|
|
|
145
|
-
|
|
209
|
+
---
|
|
146
210
|
|
|
147
|
-
|
|
148
|
-
| --- | --- | --- |
|
|
149
|
-
| **macOS** | Starmux 0.10.0 local and published-package workflow tested | SwiftUI development build |
|
|
150
|
-
| **Linux** | Preview; earlier Core tests passed on Ubuntu arm64; new CLI release acceptance pending | Not available |
|
|
151
|
-
| **Windows** | Preview implementation; native validation pending | Not available |
|
|
211
|
+
## Native macOS App
|
|
152
212
|
|
|
153
|
-
|
|
213
|
+
Prefer a visual interface? The native SwiftUI menu bar App puts service health, workspace permissions, live MCP connection topology, and recent tool calls in one compact panel.
|
|
154
214
|
|
|
155
|
-
|
|
215
|
+
<div align="center">
|
|
216
|
+
<img src="docs/assets/starmux-app-en.png" width="380" alt="Starmux native macOS menu bar app showing service status, MCP connection topology, and recent tool calls" />
|
|
217
|
+
<p><sub>Native macOS Menu Bar App (English & Chinese) · Controls the same instances as the CLI</sub></p>
|
|
218
|
+
</div>
|
|
156
219
|
|
|
157
|
-
|
|
220
|
+
The App and CLI manage the same underlying instances. Quitting the App stops only instances started by the App; background instances started via CLI keep running.
|
|
158
221
|
|
|
159
|
-
|
|
222
|
+
| Host Platform | Core & CLI | Native Menu Bar App |
|
|
223
|
+
| :--- | :--- | :--- |
|
|
224
|
+
| **macOS** (`Apple Silicon` / `Intel`) | ✅ Tested | 🧪 Development Build ([App Build Guide](app/README.md)) |
|
|
225
|
+
| **Linux** (`x64` / `ARM64` / **NVIDIA Jetson**) | ✅ Tested | — |
|
|
226
|
+
| **Windows** | ✅ Tested | — |
|
|
160
227
|
|
|
161
|
-
|
|
228
|
+
---
|
|
162
229
|
|
|
163
|
-
|
|
164
|
-
- [Authentication](docs/ACCESS_CONTROL.md) and [permission boundaries](docs/SECURITY.md)
|
|
165
|
-
- [Migrate from Mac MCP Gateway](docs/MIGRATION.md)
|
|
166
|
-
- [Client compatibility](docs/CLIENTS.md), [platform support](docs/PLATFORMS.md), and [release status](docs/RELEASE_STATUS.md)
|
|
167
|
-
- [Contributing](CONTRIBUTING.md) and [architecture](docs/ARCHITECTURE.md)
|
|
230
|
+
## Documentation & Advanced Guides
|
|
168
231
|
|
|
169
|
-
Starmux
|
|
232
|
+
Starmux began with the [community request for a self-hosted Desktop Commander workbench](https://github.com/wonderwhy-er/DesktopCommanderMCP/issues/714) and is evolving into a universal MCP router and programmable middleware for multi-server environments.
|
|
170
233
|
|
|
171
|
-
|
|
234
|
+
Explore the `docs/` guides by topic:
|
|
172
235
|
|
|
173
|
-
|
|
236
|
+
- **🌐 Remote Networking & Auth**
|
|
237
|
+
- [Remote Setup (HTTPS & Tunnel)](docs/REMOTE_SETUP.md) — Step-by-step Caddy reverse proxy, custom TLS / port `8443`, IPv6, and Tunnel setup.
|
|
238
|
+
- [Access Control & API Keys](docs/ACCESS_CONTROL.md) — Creating/revoking Bearer API keys, expiration policies, and using the local Web Access Console.
|
|
239
|
+
- [ChatGPT Web & OAuth 2.0](docs/OAUTH.md) & [Connect ChatGPT](docs/CONNECT_CHATGPT.md) — Configuring Keycloak OAuth 2.0 + PKCE and OpenAI Secure MCP Tunnel.
|
|
240
|
+
- **⚙️ Advanced Configuration & Security**
|
|
241
|
+
- [Multi-Instance & JSONC Config](docs/INSTANCES.md) — Managing multiple isolated workspaces, hot-reloading `config.jsonc`, mounting extra `stdio` MCP upstreams, and secrets.
|
|
242
|
+
- [Security & Workspace Permissions](docs/SECURITY.md) — Understanding Read-Only vs. Coding Mode, directory grants, tool deny rules, and macOS privacy boundaries.
|
|
243
|
+
- [Client Compatibility](docs/CLIENTS.md) & [Platform Details](docs/PLATFORMS.md) — Tested AI client matrix and macOS / Linux / Windows runtime specifics.
|
|
244
|
+
- **🛠️ Reference & Project**
|
|
245
|
+
- [Documentation Index](docs/README.md) · [Installation](docs/INSTALL.md) · [Contributing](CONTRIBUTING.md)
|
|
174
246
|
|
|
175
247
|
## License
|
|
176
248
|
|
|
177
|
-
Starmux original code is [MIT
|
|
249
|
+
Starmux original code is [MIT Licensed](LICENSE). [Desktop Commander](https://github.com/wonderwhy-er/DesktopCommanderMCP) is bundled as a pinned dependency and retains its own license and notices. Starmux is an independent open-source project and is not endorsed by Desktop Commander or AI client vendors.
|
package/SECURITY.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Security policy
|
|
2
2
|
|
|
3
|
-
**Status:** Community preview, version 0.
|
|
3
|
+
**Status:** Community preview, version 0.11.0. Optional Bearer API key and OAuth authentication with revocation are supported.
|
|
4
4
|
|
|
5
|
-
Please **do not post security vulnerabilities, credentials or user data in public GitHub issues**.
|
|
5
|
+
Please **do not post security vulnerabilities, credentials or user data in public GitHub issues**. Use **GitHub Private Vulnerability Reporting** to report security issues privately.
|
|
6
6
|
|
|
7
|
-
Do not expose the raw Gateway HTTP port to the public internet. Read [security and trust boundaries](docs/SECURITY.md).
|
|
7
|
+
Do not expose the raw Gateway HTTP port (`127.0.0.1`) to the public internet without an authenticated reverse proxy or tunnel. Read [security and trust boundaries](docs/SECURITY.md).
|
|
@@ -14,11 +14,12 @@ const digest = (value) => createHash('sha256').update(value).digest('hex');
|
|
|
14
14
|
const equal = (a, b) => a.length === b.length && timingSafeEqual(Buffer.from(a), Buffer.from(b));
|
|
15
15
|
export const defaultAccessFile = () => process.env.MAC_MCP_ACCESS_FILE || path.join(homedir(), '.config', 'starmux', 'access-v1.json');
|
|
16
16
|
const active = (c) => !c.revokedAt && (!c.expiresAt || Date.parse(c.expiresAt) > Date.now());
|
|
17
|
-
export async function createAccessControl(file = defaultAccessFile()) {
|
|
17
|
+
export async function createAccessControl(file = defaultAccessFile(), options = {}) {
|
|
18
18
|
if (!path.isAbsolute(file))
|
|
19
19
|
throw Error('ACCESS_FILE_ABSOLUTE_REQUIRED');
|
|
20
20
|
const folder = path.dirname(file);
|
|
21
|
-
|
|
21
|
+
if (options.initialize !== false)
|
|
22
|
+
createPrivateDirectory(folder);
|
|
22
23
|
async function readPrivate(target) {
|
|
23
24
|
const st = await lstat(target);
|
|
24
25
|
if (!st.isFile() || st.nlink !== 1 || !ownedByUser(target, st, true) || st.size > 262144)
|
|
@@ -71,27 +72,29 @@ export async function createAccessControl(file = defaultAccessFile()) {
|
|
|
71
72
|
throw Error('OAUTH_CONFIGURATION_REQUIRED');
|
|
72
73
|
return data;
|
|
73
74
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
catch (e) {
|
|
79
|
-
if (!(e instanceof Error && 'code' in e && e.code === 'ENOENT'))
|
|
80
|
-
throw e;
|
|
81
|
-
// A persistent marker prevents a deleted store from silently resetting
|
|
82
|
-
// enabled authentication to ingress mode after a CLI call or restart.
|
|
75
|
+
// Status readers validate existing state without creating stores or lock files.
|
|
76
|
+
if (options.initialize !== false)
|
|
77
|
+
await locked(async () => {
|
|
83
78
|
try {
|
|
84
|
-
await
|
|
85
|
-
throw Error('ACCESS_STORE_MISSING');
|
|
79
|
+
await load();
|
|
86
80
|
}
|
|
87
|
-
catch (
|
|
88
|
-
if (!(
|
|
89
|
-
throw
|
|
81
|
+
catch (e) {
|
|
82
|
+
if (!(e instanceof Error && 'code' in e && e.code === 'ENOENT'))
|
|
83
|
+
throw e;
|
|
84
|
+
// A persistent marker prevents a deleted store from silently resetting
|
|
85
|
+
// enabled authentication to ingress mode after a CLI call or restart.
|
|
86
|
+
try {
|
|
87
|
+
await lstat(file + '.initialized');
|
|
88
|
+
throw Error('ACCESS_STORE_MISSING');
|
|
89
|
+
}
|
|
90
|
+
catch (markerError) {
|
|
91
|
+
if (!(markerError instanceof Error && 'code' in markerError && markerError.code === 'ENOENT'))
|
|
92
|
+
throw markerError;
|
|
93
|
+
}
|
|
94
|
+
await writeFile(file + '.initialized', '1\n', { flag: 'wx', mode: 0o600 });
|
|
95
|
+
await save({ version: 1, mode: 'ingress', credentials: [], oauthConnections: [] });
|
|
90
96
|
}
|
|
91
|
-
|
|
92
|
-
await save({ version: 1, mode: 'ingress', credentials: [], oauthConnections: [] });
|
|
93
|
-
}
|
|
94
|
-
});
|
|
97
|
+
});
|
|
95
98
|
const ownerFile = file + '.owner-token';
|
|
96
99
|
async function ownerToken() {
|
|
97
100
|
return locked(async () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-control.js","sourceRoot":"","sources":["../../src/access-control.ts"],"names":[],"mappings":"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","sourcesContent":["import {oauthConfigSchema,oauthConnectionSchema,validateOAuthConfig,createOAuthVerifier,OAuthError,type OAuthConfig} from './oauth-resource.js';\nimport {randomBytes,createHash,timingSafeEqual} from 'node:crypto';\nimport {lstat,readFile,writeFile,rename,rm,mkdir} from 'node:fs/promises';\nimport {homedir} from 'node:os';\nimport path from 'node:path';\nimport {z} from 'zod';\nimport {createPrivateDirectory,ownedByUser} from './platform.js';\nimport type {RequestContext} from './contracts.js';\n\nconst credential=z.object({id:z.string().regex(/^[a-f0-9]{24}$/),name:z.string().trim().min(1).max(80).regex(/^[^\\x00-\\x1f\\x7f]+$/),\n digest:z.string().regex(/^[a-f0-9]{64}$/),createdAt:z.string().datetime(),expiresAt:z.string().datetime().nullable(),\n revokedAt:z.string().datetime().nullable(),lastUsedAt:z.string().datetime().nullable(),permission:z.literal('owner-default')}).strict();\nconst schema=z.object({version:z.literal(1),mode:z.enum(['ingress','api-key','oauth']),credentials:z.array(credential).max(256),\n oauth:oauthConfigSchema.optional(),oauthConnections:z.array(oauthConnectionSchema).max(512).default([])}).strict();\ntype State=z.infer<typeof schema>;\nconst digest=(value:string)=>createHash('sha256').update(value).digest('hex');\nconst equal=(a:string,b:string)=>a.length===b.length&&timingSafeEqual(Buffer.from(a),Buffer.from(b));\nexport const defaultAccessFile=()=>process.env.MAC_MCP_ACCESS_FILE||path.join(homedir(),'.config','starmux','access-v1.json');\nconst active=(c:Pick<z.infer<typeof credential>,'revokedAt'|'expiresAt'>)=>!c.revokedAt&&(!c.expiresAt||Date.parse(c.expiresAt)>Date.now());\nexport async function createAccessControl(file=defaultAccessFile()){\n if(!path.isAbsolute(file))throw Error('ACCESS_FILE_ABSOLUTE_REQUIRED');\n const folder=path.dirname(file);createPrivateDirectory(folder);\n async function readPrivate(target:string){\n const st=await lstat(target);\n if(!st.isFile()||st.nlink!==1||!ownedByUser(target,st,true)||st.size>262144)throw Error('ACCESS_FILE_UNSAFE');\n return readFile(target,'utf8');\n }\n async function save(data:State){\n schema.parse(data);\n const temp=file+'.'+randomBytes(12).toString('hex');\n try{await writeFile(temp,JSON.stringify(data,null,2)+'\\n',{flag:'wx',mode:0o600});await rename(temp,file);}\n finally{await rm(temp,{force:true});}\n }\n // A filesystem lock serializes mutations across CLI and Core processes.\n async function locked<T>(action:()=>Promise<T>):Promise<T>{\n const lock=file+'.lock';let acquired=false;\n for(let i=0;i<100;i++){\n try{await mkdir(lock,{mode:0o700});acquired=true;break;}\n catch(e){if(!(e instanceof Error&&'code' in e&&e.code==='EEXIST'))throw e;await new Promise(r=>setTimeout(r,20));}\n }\n if(!acquired)throw Error('ACCESS_STORE_BUSY');\n try{return await action();}finally{await rm(lock,{recursive:true,force:true});}\n }\n async function load(){\n if(!ownedByUser(folder,await lstat(folder),true))throw Error('ACCESS_DIRECTORY_UNSAFE');\n const data=schema.parse(JSON.parse(await readPrivate(file)) as unknown);\n if(data.oauth)validateOAuthConfig(data.oauth);\n if(data.mode==='oauth'&&!data.oauth)throw Error('OAUTH_CONFIGURATION_REQUIRED');\n return data;\n }\n await locked(async()=>{\n try{await load();}catch(e){\n if(!(e instanceof Error&&'code' in e&&e.code==='ENOENT'))throw e;\n // A persistent marker prevents a deleted store from silently resetting\n // enabled authentication to ingress mode after a CLI call or restart.\n try{await lstat(file+'.initialized');throw Error('ACCESS_STORE_MISSING');}\n catch(markerError){\n if(!(markerError instanceof Error&&'code' in markerError&&markerError.code==='ENOENT'))throw markerError;\n }\n await writeFile(file+'.initialized','1\\n',{flag:'wx',mode:0o600});\n await save({version:1,mode:'ingress',credentials:[],oauthConnections:[]});\n }\n });\n const ownerFile=file+'.owner-token';\n async function ownerToken(){\n return locked(async()=>{\n try{return (await readPrivate(ownerFile)).trim();}catch(e){\n if(!(e instanceof Error&&'code' in e&&e.code==='ENOENT'))throw e;\n const token=randomBytes(32).toString('hex');\n await writeFile(ownerFile,token+'\\n',{flag:'wx',mode:0o600});return token;\n }\n });\n }\n async function authorized(token:unknown){\n if(typeof token!=='string'||! /^[a-f0-9]{64}$/.test(token))return false;\n try{return equal(token,(await readPrivate(ownerFile)).trim());}catch{return false;}\n }\n async function snapshot(){\n const data=await load();return {mode:data.mode,permission:'owner-default' as const,oauth:data.oauth??null,oauthConnections:data.oauthConnections,\n credentials:data.credentials.map(({digest:_,...c})=>({...c,status:c.revokedAt?'revoked':active(c)?'active':'expired'}))};\n }\n async function create(name:string,expiresAt:string|null=null,activate=false){\n const secret='smx_'+randomBytes(32).toString('base64url');\n const c=credential.parse({id:randomBytes(12).toString('hex'),name,digest:digest(secret),createdAt:new Date().toISOString(),\n expiresAt,revokedAt:null,lastUsedAt:null,permission:'owner-default'});\n if(!active(c))throw Error('ACCESS_EXPIRY_MUST_BE_FUTURE');\n await locked(async()=>{const data=await load();data.credentials.push(c);if(activate)data.mode='api-key';await save(data);});\n return {id:c.id,name:c.name,key:secret,permission:c.permission};\n }\n async function setMode(mode:'ingress'|'api-key'|'oauth'){\n await locked(async()=>{const data=await load();\n if(mode==='api-key'&&!data.credentials.some(active))throw Error('ACCESS_ACTIVE_KEY_REQUIRED');\n if(mode==='oauth'&&!data.oauth)throw Error('OAUTH_CONFIGURATION_REQUIRED');\n data.mode=mode;await save(data);});\n }\n async function revoke(id:string){\n await locked(async()=>{const data=await load();const c=data.credentials.find(c=>c.id===id);\n if(!c)throw Error('ACCESS_CREDENTIAL_NOT_FOUND');c.revokedAt??=new Date().toISOString();await save(data);});\n }\n async function configureOAuth(value:unknown){\n const config=validateOAuthConfig(value);\n await locked(async()=>{const data=await load();data.oauth=config;await save(data);});\n }\n async function revokeOAuth(id:string){\n await locked(async()=>{const data=await load();const c=data.oauthConnections.find(c=>c.id===id);\n if(!c)throw Error('OAUTH_CONNECTION_NOT_FOUND');c.revokedAt??=new Date().toISOString();await save(data);});\n }\n async function oauthSettings(){const data=await load();return data.mode==='oauth'?data.oauth!:null;}\n let verifier:{signature:string;verify:ReturnType<typeof createOAuthVerifier>}|undefined;\n async function authenticateOAuth(config:OAuthConfig,header:string|undefined):Promise<RequestContext['identity']>{\n const signature=JSON.stringify(config);\n if(verifier?.signature!==signature)verifier={signature,verify:createOAuthVerifier(config)};\n const verified=await verifier.verify(header);\n await locked(async()=>{\n const data=await load();\n if(data.mode!=='oauth'||JSON.stringify(data.oauth)!==signature)throw new OAuthError(401,'OAUTH_CONFIGURATION_CHANGED');\n const c=data.oauthConnections.find(c=>c.id===verified.id);\n if(c?.revokedAt)throw new OAuthError(401,'OAUTH_CONNECTION_REVOKED');\n const now=new Date().toISOString();\n if(c)c.lastUsedAt=now;\n else{\n if(data.oauthConnections.length>=512)throw new OAuthError(503,'OAUTH_CONNECTION_LIMIT');\n data.oauthConnections.push({id:verified.id,subjectHash:verified.subjectHash,clientId:verified.clientId,\n createdAt:now,lastUsedAt:now,revokedAt:null,permission:'owner-default'});\n }\n await save(data);\n });\n return {kind:'verified-oauth',connectionId:verified.id,subjectHash:verified.subjectHash,scopes:verified.scopes,permission:'owner-default'};\n }\n async function authenticate(header:string|undefined):Promise<RequestContext['identity']>{\n const data=await load();\n if(data.mode==='ingress')return {kind:'unverified-local'};\n if(data.mode==='oauth')return authenticateOAuth(data.oauth!,header);\n if(!header||!/^Bearer (?:mgw_|smx_)[A-Za-z0-9_-]{43}$/i.test(header))throw Error('ACCESS_UNAUTHORIZED');\n const hash=digest(header.slice(7));const c=data.credentials.find(c=>equal(c.digest,hash)&&active(c));\n if(!c)throw Error('ACCESS_UNAUTHORIZED');\n // Recheck under the mutation lock: revocation cannot be lost to a usage update.\n await locked(async()=>{const current=await load();const found=current.credentials.find(v=>v.id===c.id);\n if(current.mode!=='api-key'||!found||!active(found))throw Error('ACCESS_UNAUTHORIZED');\n found.lastUsedAt=new Date().toISOString();await save(current);});\n return {kind:'verified-api-key',credentialId:c.id,permission:'owner-default'};\n }\n return {file,snapshot,create,setMode,revoke,authenticate,ownerToken,authorized,configureOAuth,revokeOAuth,oauthSettings};\n}\nexport type AccessControl=Awaited<ReturnType<typeof createAccessControl>>;\n"]}
|
|
1
|
+
{"version":3,"file":"access-control.js","sourceRoot":"","sources":["../../src/access-control.ts"],"names":[],"mappings":"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","sourcesContent":["import {oauthConfigSchema,oauthConnectionSchema,validateOAuthConfig,createOAuthVerifier,OAuthError,type OAuthConfig} from './oauth-resource.js';\nimport {randomBytes,createHash,timingSafeEqual} from 'node:crypto';\nimport {lstat,readFile,writeFile,rename,rm,mkdir} from 'node:fs/promises';\nimport {homedir} from 'node:os';\nimport path from 'node:path';\nimport {z} from 'zod';\nimport {createPrivateDirectory,ownedByUser} from './platform.js';\nimport type {RequestContext} from './contracts.js';\n\nconst credential=z.object({id:z.string().regex(/^[a-f0-9]{24}$/),name:z.string().trim().min(1).max(80).regex(/^[^\\x00-\\x1f\\x7f]+$/),\n digest:z.string().regex(/^[a-f0-9]{64}$/),createdAt:z.string().datetime(),expiresAt:z.string().datetime().nullable(),\n revokedAt:z.string().datetime().nullable(),lastUsedAt:z.string().datetime().nullable(),permission:z.literal('owner-default')}).strict();\nconst schema=z.object({version:z.literal(1),mode:z.enum(['ingress','api-key','oauth']),credentials:z.array(credential).max(256),\n oauth:oauthConfigSchema.optional(),oauthConnections:z.array(oauthConnectionSchema).max(512).default([])}).strict();\ntype State=z.infer<typeof schema>;\nconst digest=(value:string)=>createHash('sha256').update(value).digest('hex');\nconst equal=(a:string,b:string)=>a.length===b.length&&timingSafeEqual(Buffer.from(a),Buffer.from(b));\nexport const defaultAccessFile=()=>process.env.MAC_MCP_ACCESS_FILE||path.join(homedir(),'.config','starmux','access-v1.json');\nconst active=(c:Pick<z.infer<typeof credential>,'revokedAt'|'expiresAt'>)=>!c.revokedAt&&(!c.expiresAt||Date.parse(c.expiresAt)>Date.now());\nexport async function createAccessControl(file=defaultAccessFile(),options:{initialize?:boolean}={}){\n if(!path.isAbsolute(file))throw Error('ACCESS_FILE_ABSOLUTE_REQUIRED');\n const folder=path.dirname(file);if(options.initialize!==false)createPrivateDirectory(folder);\n async function readPrivate(target:string){\n const st=await lstat(target);\n if(!st.isFile()||st.nlink!==1||!ownedByUser(target,st,true)||st.size>262144)throw Error('ACCESS_FILE_UNSAFE');\n return readFile(target,'utf8');\n }\n async function save(data:State){\n schema.parse(data);\n const temp=file+'.'+randomBytes(12).toString('hex');\n try{await writeFile(temp,JSON.stringify(data,null,2)+'\\n',{flag:'wx',mode:0o600});await rename(temp,file);}\n finally{await rm(temp,{force:true});}\n }\n // A filesystem lock serializes mutations across CLI and Core processes.\n async function locked<T>(action:()=>Promise<T>):Promise<T>{\n const lock=file+'.lock';let acquired=false;\n for(let i=0;i<100;i++){\n try{await mkdir(lock,{mode:0o700});acquired=true;break;}\n catch(e){if(!(e instanceof Error&&'code' in e&&e.code==='EEXIST'))throw e;await new Promise(r=>setTimeout(r,20));}\n }\n if(!acquired)throw Error('ACCESS_STORE_BUSY');\n try{return await action();}finally{await rm(lock,{recursive:true,force:true});}\n }\n async function load(){\n if(!ownedByUser(folder,await lstat(folder),true))throw Error('ACCESS_DIRECTORY_UNSAFE');\n const data=schema.parse(JSON.parse(await readPrivate(file)) as unknown);\n if(data.oauth)validateOAuthConfig(data.oauth);\n if(data.mode==='oauth'&&!data.oauth)throw Error('OAUTH_CONFIGURATION_REQUIRED');\n return data;\n }\n // Status readers validate existing state without creating stores or lock files.\n if(options.initialize!==false)await locked(async()=>{\n try{await load();}catch(e){\n if(!(e instanceof Error&&'code' in e&&e.code==='ENOENT'))throw e;\n // A persistent marker prevents a deleted store from silently resetting\n // enabled authentication to ingress mode after a CLI call or restart.\n try{await lstat(file+'.initialized');throw Error('ACCESS_STORE_MISSING');}\n catch(markerError){\n if(!(markerError instanceof Error&&'code' in markerError&&markerError.code==='ENOENT'))throw markerError;\n }\n await writeFile(file+'.initialized','1\\n',{flag:'wx',mode:0o600});\n await save({version:1,mode:'ingress',credentials:[],oauthConnections:[]});\n }\n });\n const ownerFile=file+'.owner-token';\n async function ownerToken(){\n return locked(async()=>{\n try{return (await readPrivate(ownerFile)).trim();}catch(e){\n if(!(e instanceof Error&&'code' in e&&e.code==='ENOENT'))throw e;\n const token=randomBytes(32).toString('hex');\n await writeFile(ownerFile,token+'\\n',{flag:'wx',mode:0o600});return token;\n }\n });\n }\n async function authorized(token:unknown){\n if(typeof token!=='string'||! /^[a-f0-9]{64}$/.test(token))return false;\n try{return equal(token,(await readPrivate(ownerFile)).trim());}catch{return false;}\n }\n async function snapshot(){\n const data=await load();return {mode:data.mode,permission:'owner-default' as const,oauth:data.oauth??null,oauthConnections:data.oauthConnections,\n credentials:data.credentials.map(({digest:_,...c})=>({...c,status:c.revokedAt?'revoked':active(c)?'active':'expired'}))};\n }\n async function create(name:string,expiresAt:string|null=null,activate=false){\n const secret='smx_'+randomBytes(32).toString('base64url');\n const c=credential.parse({id:randomBytes(12).toString('hex'),name,digest:digest(secret),createdAt:new Date().toISOString(),\n expiresAt,revokedAt:null,lastUsedAt:null,permission:'owner-default'});\n if(!active(c))throw Error('ACCESS_EXPIRY_MUST_BE_FUTURE');\n await locked(async()=>{const data=await load();data.credentials.push(c);if(activate)data.mode='api-key';await save(data);});\n return {id:c.id,name:c.name,key:secret,permission:c.permission};\n }\n async function setMode(mode:'ingress'|'api-key'|'oauth'){\n await locked(async()=>{const data=await load();\n if(mode==='api-key'&&!data.credentials.some(active))throw Error('ACCESS_ACTIVE_KEY_REQUIRED');\n if(mode==='oauth'&&!data.oauth)throw Error('OAUTH_CONFIGURATION_REQUIRED');\n data.mode=mode;await save(data);});\n }\n async function revoke(id:string){\n await locked(async()=>{const data=await load();const c=data.credentials.find(c=>c.id===id);\n if(!c)throw Error('ACCESS_CREDENTIAL_NOT_FOUND');c.revokedAt??=new Date().toISOString();await save(data);});\n }\n async function configureOAuth(value:unknown){\n const config=validateOAuthConfig(value);\n await locked(async()=>{const data=await load();data.oauth=config;await save(data);});\n }\n async function revokeOAuth(id:string){\n await locked(async()=>{const data=await load();const c=data.oauthConnections.find(c=>c.id===id);\n if(!c)throw Error('OAUTH_CONNECTION_NOT_FOUND');c.revokedAt??=new Date().toISOString();await save(data);});\n }\n async function oauthSettings(){const data=await load();return data.mode==='oauth'?data.oauth!:null;}\n let verifier:{signature:string;verify:ReturnType<typeof createOAuthVerifier>}|undefined;\n async function authenticateOAuth(config:OAuthConfig,header:string|undefined):Promise<RequestContext['identity']>{\n const signature=JSON.stringify(config);\n if(verifier?.signature!==signature)verifier={signature,verify:createOAuthVerifier(config)};\n const verified=await verifier.verify(header);\n await locked(async()=>{\n const data=await load();\n if(data.mode!=='oauth'||JSON.stringify(data.oauth)!==signature)throw new OAuthError(401,'OAUTH_CONFIGURATION_CHANGED');\n const c=data.oauthConnections.find(c=>c.id===verified.id);\n if(c?.revokedAt)throw new OAuthError(401,'OAUTH_CONNECTION_REVOKED');\n const now=new Date().toISOString();\n if(c)c.lastUsedAt=now;\n else{\n if(data.oauthConnections.length>=512)throw new OAuthError(503,'OAUTH_CONNECTION_LIMIT');\n data.oauthConnections.push({id:verified.id,subjectHash:verified.subjectHash,clientId:verified.clientId,\n createdAt:now,lastUsedAt:now,revokedAt:null,permission:'owner-default'});\n }\n await save(data);\n });\n return {kind:'verified-oauth',connectionId:verified.id,subjectHash:verified.subjectHash,scopes:verified.scopes,permission:'owner-default'};\n }\n async function authenticate(header:string|undefined):Promise<RequestContext['identity']>{\n const data=await load();\n if(data.mode==='ingress')return {kind:'unverified-local'};\n if(data.mode==='oauth')return authenticateOAuth(data.oauth!,header);\n if(!header||!/^Bearer (?:mgw_|smx_)[A-Za-z0-9_-]{43}$/i.test(header))throw Error('ACCESS_UNAUTHORIZED');\n const hash=digest(header.slice(7));const c=data.credentials.find(c=>equal(c.digest,hash)&&active(c));\n if(!c)throw Error('ACCESS_UNAUTHORIZED');\n // Recheck under the mutation lock: revocation cannot be lost to a usage update.\n await locked(async()=>{const current=await load();const found=current.credentials.find(v=>v.id===c.id);\n if(current.mode!=='api-key'||!found||!active(found))throw Error('ACCESS_UNAUTHORIZED');\n found.lastUsedAt=new Date().toISOString();await save(current);});\n return {kind:'verified-api-key',credentialId:c.id,permission:'owner-default'};\n }\n return {file,snapshot,create,setMode,revoke,authenticate,ownerToken,authorized,configureOAuth,revokeOAuth,oauthSettings};\n}\nexport type AccessControl=Awaited<ReturnType<typeof createAccessControl>>;\n"]}
|
|
@@ -7,9 +7,15 @@ export const accessHeaders = Object.freeze({ ...permissionHeaders, 'content-secu
|
|
|
7
7
|
const e = (s) => String(s ?? '').replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
|
8
8
|
export function accessPage(state, created) {
|
|
9
9
|
const form = (op, fields, label) => `<form method="post" action="/access"><input type="hidden" name="op" value="${op}">${fields}<label>Owner management token <input type="password" name="token" required autocomplete="off"></label><button>${label}</button></form>`;
|
|
10
|
-
return `<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>
|
|
10
|
+
return `<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Starmux · Access Control</title><style>body{font:16px system-ui;max-width:960px;margin:40px auto;padding:0 24px;background:#f5f7fa;color:#243249}section{background:white;border:1px solid #dae0e8;border-radius:12px;padding:24px;margin:20px 0}input,select,button{font:inherit;padding:8px;margin:8px}label{display:block}code{overflow-wrap:anywhere}td,th{text-align:left;padding:10px;border-bottom:1px solid #ddd}table{width:100%}</style><h1>Starmux · Access Control</h1>
|
|
11
11
|
<p><a href="/dashboard">Dashboard</a> · <a href="/permissions">Workspace permissions</a></p>
|
|
12
|
-
<section><h2>${state.mode === 'ingress' ? '
|
|
12
|
+
<section><h2>${state.mode === 'ingress' ? 'None (local or ingress-managed)' : state.mode === 'oauth' ? 'OAuth' : 'Bearer API Key'}</h2><p>Permission: Owner default. Credentials do not change workspace, tool or operating-system permissions.</p><p>${state.mode === 'ingress' ? 'Starmux does not verify a token. Restrict remote access through your HTTPS proxy or tunnel.' : 'Every MCP request requires an active credential. Missing, invalid, expired or revoked credentials are rejected.'}</p>
|
|
13
|
+
<table><tr><th>MCP Client Field</th><th>Value to Copy</th></tr>
|
|
14
|
+
<tr><td>Transport</td><td><code>Streamable HTTP</code></td></tr>
|
|
15
|
+
<tr><td>URL (Local)</td><td><code>http://127.0.0.1:<PORT>/mcp</code></td></tr>
|
|
16
|
+
<tr><td>URL (Remote)</td><td><code>${state.mode === 'oauth' && state.oauth ? e(state.oauth.resource) : 'https://<your-domain-or-tunnel>/mcp'}</code></td></tr>
|
|
17
|
+
<tr><td>Authentication</td><td><code>${state.mode === 'ingress' ? 'None (or optional Bearer API key)' : state.mode === 'api-key' ? 'Bearer API key — Header: Authorization: Bearer <YOUR_API_KEY>' : `OAuth — Client ID: ${e(state.oauth?.clientId ?? '')} · Scope: ${e(state.oauth?.scope ?? 'gateway:access')} · Issuer: ${e(state.oauth?.issuer ?? '')}`}</code></td></tr>
|
|
18
|
+
</table></section>
|
|
13
19
|
${created ? `<section><h2>Copy this key now</h2><code>${e(created.key)}</code><p>This key is shown only in this response. Keep it private; it grants the owner-approved MCP capabilities.</p></section>` : ''}
|
|
14
20
|
<section><h2>Client credentials</h2><table><tr><th>Name / ID</th><th>Status / Revoked</th><th>Created / Last used / Expiry</th></tr>${state.credentials.map(c => `<tr><td>${e(c.name)}<br><code>${e(c.id)}</code></td><td>${e(c.status)}<br>${e(c.revokedAt || '—')}</td><td>${e(c.createdAt)}<br>${e(c.lastUsedAt || 'Never')}<br>${e(c.expiresAt || 'No expiry')}</td></tr>`).join('') || '<tr><td colspan="3">No credentials</td></tr>'}</table></section>
|
|
15
21
|
<section><h2>Create credential</h2>${form('create', '<label>Name <input name="name" maxlength="80" required></label><label>Expiry (optional, UTC ISO 8601) <input name="expiresAt" placeholder="2027-01-01T00:00:00.000Z"></label>', 'Create')}</section>
|