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.
Files changed (136) hide show
  1. package/CONTRIBUTING.md +1 -1
  2. package/README.md +169 -97
  3. package/SECURITY.md +3 -3
  4. package/dist/src/access-control.js +23 -20
  5. package/dist/src/access-control.js.map +1 -1
  6. package/dist/src/access-dashboard.js +8 -2
  7. package/dist/src/access-dashboard.js.map +1 -1
  8. package/dist/src/cli/instances.js +87 -14
  9. package/dist/src/cli/instances.js.map +1 -1
  10. package/dist/src/cli/mac-mcp-gateway.js +4 -3
  11. package/dist/src/cli/mac-mcp-gateway.js.map +1 -1
  12. package/dist/src/cli/starmux-status.js +122 -0
  13. package/dist/src/cli/starmux-status.js.map +1 -0
  14. package/dist/src/cli/starmux.js +222 -49
  15. package/dist/src/cli/starmux.js.map +1 -1
  16. package/dist/src/deny-dashboard.js +2 -2
  17. package/dist/src/deny-dashboard.js.map +1 -1
  18. package/dist/src/instance-runtime.js +11 -2
  19. package/dist/src/instance-runtime.js.map +1 -1
  20. package/dist/src/instance-store.js +3 -1
  21. package/dist/src/instance-store.js.map +1 -1
  22. package/dist/src/ops-dashboard.js +4 -4
  23. package/dist/src/ops-dashboard.js.map +1 -1
  24. package/dist/src/permissions-dashboard.js +2 -2
  25. package/dist/src/permissions-dashboard.js.map +1 -1
  26. package/dist/src/runtime-paths.js +3 -3
  27. package/dist/src/runtime-paths.js.map +1 -1
  28. package/dist/src/starmux-migration.js +1 -1
  29. package/dist/src/starmux-migration.js.map +1 -1
  30. package/dist/vendor/desktop-commander/LICENSE +21 -0
  31. package/dist/vendor/desktop-commander/dist/bootstrap.js +22 -0
  32. package/dist/vendor/desktop-commander/dist/command-manager.js +246 -0
  33. package/dist/vendor/desktop-commander/dist/config-field-definitions.js +37 -0
  34. package/dist/vendor/desktop-commander/dist/config-manager.js +634 -0
  35. package/dist/vendor/desktop-commander/dist/config-recovery.js +81 -0
  36. package/dist/vendor/desktop-commander/dist/config.js +19 -0
  37. package/dist/vendor/desktop-commander/dist/custom-stdio.js +358 -0
  38. package/dist/vendor/desktop-commander/dist/error-handlers.js +15 -0
  39. package/dist/vendor/desktop-commander/dist/handlers/edit-search-handlers.js +6 -0
  40. package/dist/vendor/desktop-commander/dist/handlers/filesystem-handlers.js +450 -0
  41. package/dist/vendor/desktop-commander/dist/handlers/history-handlers.js +67 -0
  42. package/dist/vendor/desktop-commander/dist/handlers/index.js +7 -0
  43. package/dist/vendor/desktop-commander/dist/handlers/process-handlers.js +15 -0
  44. package/dist/vendor/desktop-commander/dist/handlers/search-answers.js +172 -0
  45. package/dist/vendor/desktop-commander/dist/handlers/search-handlers.js +136 -0
  46. package/dist/vendor/desktop-commander/dist/handlers/terminal-handlers.js +35 -0
  47. package/dist/vendor/desktop-commander/dist/index.js +88 -0
  48. package/dist/vendor/desktop-commander/dist/search-manager.js +1262 -0
  49. package/dist/vendor/desktop-commander/dist/server.js +1414 -0
  50. package/dist/vendor/desktop-commander/dist/terminal-manager.js +757 -0
  51. package/dist/vendor/desktop-commander/dist/tools/config.js +211 -0
  52. package/dist/vendor/desktop-commander/dist/tools/edit.js +455 -0
  53. package/dist/vendor/desktop-commander/dist/tools/feedback.js +10 -0
  54. package/dist/vendor/desktop-commander/dist/tools/filesystem.js +1047 -0
  55. package/dist/vendor/desktop-commander/dist/tools/fuzzySearch.js +74 -0
  56. package/dist/vendor/desktop-commander/dist/tools/fuzzySearchCore.js +125 -0
  57. package/dist/vendor/desktop-commander/dist/tools/improved-process-tools.js +726 -0
  58. package/dist/vendor/desktop-commander/dist/tools/mime-types.js +28 -0
  59. package/dist/vendor/desktop-commander/dist/tools/pdf/extract-images.js +131 -0
  60. package/dist/vendor/desktop-commander/dist/tools/pdf/index.js +4 -0
  61. package/dist/vendor/desktop-commander/dist/tools/pdf/lib/pdf2md.js +78 -0
  62. package/dist/vendor/desktop-commander/dist/tools/pdf/manipulations.js +112 -0
  63. package/dist/vendor/desktop-commander/dist/tools/pdf/markdown.js +30 -0
  64. package/dist/vendor/desktop-commander/dist/tools/pdf/utils.js +45 -0
  65. package/dist/vendor/desktop-commander/dist/tools/process.js +90 -0
  66. package/dist/vendor/desktop-commander/dist/tools/prompts.js +259 -0
  67. package/dist/vendor/desktop-commander/dist/tools/schemas.js +242 -0
  68. package/dist/vendor/desktop-commander/dist/tools/usage.js +24 -0
  69. package/dist/vendor/desktop-commander/dist/types.js +1 -0
  70. package/dist/vendor/desktop-commander/dist/ui/config-editor/config-editor-runtime.js +150 -0
  71. package/dist/vendor/desktop-commander/dist/ui/config-editor/index.html +13 -0
  72. package/dist/vendor/desktop-commander/dist/ui/config-editor/styles.css +587 -0
  73. package/dist/vendor/desktop-commander/dist/ui/contracts.js +57 -0
  74. package/dist/vendor/desktop-commander/dist/ui/file-preview/index.html +16 -0
  75. package/dist/vendor/desktop-commander/dist/ui/file-preview/preview-runtime.js +471 -0
  76. package/dist/vendor/desktop-commander/dist/ui/file-preview/shared/preview-file-types.js +59 -0
  77. package/dist/vendor/desktop-commander/dist/ui/file-preview/styles.css +1631 -0
  78. package/dist/vendor/desktop-commander/dist/ui/resources.js +93 -0
  79. package/dist/vendor/desktop-commander/dist/utils/ab-test.js +122 -0
  80. package/dist/vendor/desktop-commander/dist/utils/atomic-write.js +80 -0
  81. package/dist/vendor/desktop-commander/dist/utils/capture.js +40 -0
  82. package/dist/vendor/desktop-commander/dist/utils/dockerPrompt.js +9 -0
  83. package/dist/vendor/desktop-commander/dist/utils/exit-process.js +31 -0
  84. package/dist/vendor/desktop-commander/dist/utils/feature-flags.js +18 -0
  85. package/dist/vendor/desktop-commander/dist/utils/files/base.js +5 -0
  86. package/dist/vendor/desktop-commander/dist/utils/files/binary.js +65 -0
  87. package/dist/vendor/desktop-commander/dist/utils/files/docx.js +694 -0
  88. package/dist/vendor/desktop-commander/dist/utils/files/excel.js +497 -0
  89. package/dist/vendor/desktop-commander/dist/utils/files/factory.js +168 -0
  90. package/dist/vendor/desktop-commander/dist/utils/files/image.js +89 -0
  91. package/dist/vendor/desktop-commander/dist/utils/files/index.js +14 -0
  92. package/dist/vendor/desktop-commander/dist/utils/files/pdf.js +124 -0
  93. package/dist/vendor/desktop-commander/dist/utils/files/text.js +423 -0
  94. package/dist/vendor/desktop-commander/dist/utils/fuzzySearchLogger.js +126 -0
  95. package/dist/vendor/desktop-commander/dist/utils/internal-facts.js +23 -0
  96. package/dist/vendor/desktop-commander/dist/utils/lazy-package.js +44 -0
  97. package/dist/vendor/desktop-commander/dist/utils/lineEndingHandler.js +77 -0
  98. package/dist/vendor/desktop-commander/dist/utils/logger.js +72 -0
  99. package/dist/vendor/desktop-commander/dist/utils/mcp-ui-ab-test.js +62 -0
  100. package/dist/vendor/desktop-commander/dist/utils/open-browser.js +2 -0
  101. package/dist/vendor/desktop-commander/dist/utils/process-detection.js +260 -0
  102. package/dist/vendor/desktop-commander/dist/utils/process-tree.js +223 -0
  103. package/dist/vendor/desktop-commander/dist/utils/rename.js +74 -0
  104. package/dist/vendor/desktop-commander/dist/utils/ripgrep-resolver.js +69 -0
  105. package/dist/vendor/desktop-commander/dist/utils/shell.js +187 -0
  106. package/dist/vendor/desktop-commander/dist/utils/system-info.js +727 -0
  107. package/dist/vendor/desktop-commander/dist/utils/terminal-timestamps.js +18 -0
  108. package/dist/vendor/desktop-commander/dist/utils/toolHistory.js +309 -0
  109. package/dist/vendor/desktop-commander/dist/utils/trackTools.js +55 -0
  110. package/dist/vendor/desktop-commander/dist/utils/unsupportedParams.js +66 -0
  111. package/dist/vendor/desktop-commander/dist/utils/usageTracker.js +225 -0
  112. package/dist/vendor/desktop-commander/dist/utils/welcome-onboarding.js +2 -0
  113. package/dist/vendor/desktop-commander/dist/utils/withTimeout.js +85 -0
  114. package/dist/vendor/desktop-commander/dist/version.js +1 -0
  115. package/dist/vendor/desktop-commander/package.json +15 -0
  116. package/docs/ACCESS_CONTROL.md +5 -5
  117. package/docs/CLIENTS.md +11 -19
  118. package/docs/CONNECT_CHATGPT.md +4 -6
  119. package/docs/INSTALL.md +9 -5
  120. package/docs/INSTANCES.md +5 -2
  121. package/docs/OAUTH.md +6 -14
  122. package/docs/PLATFORMS.md +4 -4
  123. package/docs/README.md +2 -11
  124. package/docs/REMOTE_SETUP.md +13 -13
  125. package/docs/SECURITY.md +5 -5
  126. package/docs/assets/starmux-architecture.svg +315 -0
  127. package/package.json +14 -2
  128. package/docs/ARCHITECTURE.md +0 -51
  129. package/docs/MIGRATION.md +0 -37
  130. package/docs/RELEASE_STATUS.md +0 -31
  131. package/docs/TYPESCRIPT_MIGRATION.md +0 -56
  132. package/docs/VALIDATION_OAUTH.md +0 -79
  133. package/docs/VALIDATION_TUNNEL.md +0 -39
  134. package/docs/VALIDATION_WORKBUDDY.md +0 -59
  135. package/docs/assets/mcp-connection-route.svg +0 -104
  136. 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 [current preview status](docs/RELEASE_STATUS.md).
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
- ### Your computer. Your tools. Connected to your AI.
5
+ ### Give cloud AI clients secure access to your local machine.
6
6
 
7
- Connect remote AI clients to files, terminals and development tools on your own computer. **Keep your development environment. Choose what AI can do. Start, stop and revoke access on your terms.**
7
+ **Self-hosted MCP Gateway · Built-in File, Shell & Git Tools · macOS Menu Bar App · MIT**
8
8
 
9
- **Self-hosted MCP · Native macOS menu bar App · Cross-platform CLI preview · MIT**
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
- ## Your AI client here. Your tools there.
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
- Your repositories, runtimes and development tools already live on your computer. Starmux lets your AI client work with that environment through a self-hosted MCP connection: inspect a project in read-only mode, or enable coding to edit files, run tests and use Git.
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
- - **Keep the tools you know.** Work with the files and software already installed on the host.
20
- - **Choose your AI client.** Use a compatible remote MCP client through HTTPS or a protected Tunnel.
21
- - **Stay in control.** Start safely with read-only access. Enable coding deliberately, give clients their own credentials and revoke access when needed.
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
- Install Starmux on the **computer whose files and tools you want to use**. Your AI client can run elsewhere.
30
+ ---
24
31
 
25
32
  ## Quick Start
26
33
 
27
34
  ### 1. Install
28
35
 
29
- Requires Node.js **22.12 or newer**.
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: file edits, Shell and Git
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. Coding mode asks for confirmation the first time; `--code` and `--write` are equivalent. Coding tools use your OS account's permissions, with the project as their default working directory.
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 · default · running
51
- Workspace /path/to/your/project
52
- Mode Read-only
53
- Runtime background
54
- MCP http://127.0.0.1:18893/mcp
55
- Dashboard http://127.0.0.1:18893/access
56
- Logs starmux logs -f -i "default"
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. Create an API key (optional)
78
+ ### 3. Add Starmux to your AI client
60
79
 
61
- For clients that support Bearer authentication:
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
- This creates a key and enables API-key authentication immediately. Copy the key into your client's private configuration.
86
+ Depending on your AI client, configure the MCP server using either **Form UI** or **JSON**:
68
87
 
69
- ## How it connects
88
+ #### Option A — AI clients with a Form UI (e.g., ChatGPT Web, Claude)
70
89
 
71
- ![AI clients connect through HTTPS or a protected Tunnel to Starmux and workspace tools.](docs/assets/mcp-gateway-simple.svg)
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
- ```text
75
- AI client
76
- → HTTPS / protected Tunnel
77
- → authentication: ingress | API key | OAuth
78
- → Starmux at 127.0.0.1:<port>/mcp
79
- → your files and development tools
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
- HTTPS or a protected Tunnel carries the connection; authentication determines who can connect; the permission mode controls what tools can do. Start with the route your client supports:
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
- | Client or deployment | Next step |
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
- The guide connects the pieces: DNS, HTTPS/proxy or Tunnel, authentication, and a real client tool call. These network services are configured separately from Starmux.
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
- ## Everyday commands
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
- ```bash
96
- starmux status
97
- starmux verify # Local MCP handshake and directory read
98
- starmux doctor # Environment and configuration checks
99
- starmux dashboard # Open the local Access console
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
- After initialization, `up` reuses your saved workspace. Use `starmux up .` to switch it to your current directory, or pass another path. `--foreground` keeps the process in your terminal.
148
+ ### 1. Choose a Network Forwarder (for Remote / Cloud Access)
105
149
 
106
- ## A quiet place to keep an eye on things
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
- The native macOS menu bar App puts service health, workspace permissions, connection topology and recent tool activity in one compact panel. Start or stop the selected workspace, open its Access console, and keep the details close without leaving a management window open.
155
+ ### 2. Choose an Authentication Mode
109
156
 
110
- <div align="center">
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
- The App and CLI control the same instances. Quitting the App stops only instances it started; CLI background instances keep running. [App build instructions](app/README.md)
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
- ## More than one workspace
165
+ ### 3. Client Setup Guides
118
166
 
119
- ```bash
120
- starmux up ~/work/repo-a -i work --code
121
- starmux up ~/personal/blog -i blog --read
122
- starmux ls
123
- starmux down -i work
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
- starmux config edit -i blog
126
- starmux config apply -i blog
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
- Instance names are your labels. Each instance has its own port, configuration, credentials, policies and logs. [Instances and configuration →](docs/INSTANCES.md)
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
- ## AI client compatibility
195
+ ### Advanced: Running multiple workspaces (`-i`)
132
196
 
133
- These are recorded project workflows, including those validated before the Starmux rename. They are not a claim that every client, authentication mode and platform combination has passed the new release.
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
- | Client | Recorded validation |
136
- | --- | --- |
137
- | **ChatGPT web** | Tunnel + OAuth, including coding and session revocation |
138
- | **Codex** | Installed Gateway plugin through protected Tunnel |
139
- | **WorkBuddy** | HTTPS + IPv6 + API-key file reads and writes on macOS |
140
- | **Doubao (豆包)** | Owner-reported successful use; connection details not recorded |
141
- | **Claude App** | Compatibility target; end-to-end validation pending |
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
- [Exact test scope and client setup guides](docs/CLIENTS.md)
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
- ## Platform support
209
+ ---
146
210
 
147
- | Tool host | Core / CLI | Native App |
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
- [Platform details](docs/PLATFORMS.md) · [Release status](docs/RELEASE_STATUS.md). The macOS App is currently an ad-hoc developer build; a signed, notarized public download is not yet available.
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
- ## Where this is going
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
- Starmux began with the [community request for a self-hosted Desktop Commander workbench](https://github.com/wonderwhy-er/DesktopCommanderMCP/issues/714). The first priority is a clear path from a local workspace to a real tool call in your preferred AI client.
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
- The longer-term direction is a gateway for more MCP servers and clients, with programmable middleware. A public Middleware SDK and user-script plugins are not shipped. Contributions to first-user setup, client compatibility and platform testing are welcome.
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
- ## Documentation and support
228
+ ---
162
229
 
163
- - [Installation](docs/INSTALL.md) and [documentation index](docs/README.md)
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 0.11.0 is a preview. The CLI supports macOS, Linux and Windows; the native macOS App is available as a development build. See the linked compatibility pages for tested combinations.
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
- ## Built with Desktop Commander
234
+ Explore the `docs/` guides by topic:
172
235
 
173
- Starmux includes a pinned [Desktop Commander](https://github.com/wonderwhy-er/DesktopCommanderMCP) dependency, installed automatically with npm. You do not need to install it separately. Starmux supplies its own self-hosted MCP connection and lifecycle controls.
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 licensed](LICENSE). Desktop Commander retains its own license and notices. This independent project is not endorsed by Desktop Commander or AI client vendors.
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.9.0. Optional Gateway API key authentication and revocation are tested locally. Broad external-client compatibility and public Shell deployment acceptance remain unverified.
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**. Before opening the new community repository, its owner should enable **GitHub Private Vulnerability Reporting** or provide another vetted confidential reporting contact. Until that private route is published, avoid sending exploit details publicly.
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
- createPrivateDirectory(folder);
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
- await locked(async () => {
75
- try {
76
- await load();
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 lstat(file + '.initialized');
85
- throw Error('ACCESS_STORE_MISSING');
79
+ await load();
86
80
  }
87
- catch (markerError) {
88
- if (!(markerError instanceof Error && 'code' in markerError && markerError.code === 'ENOENT'))
89
- throw markerError;
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
- await writeFile(file + '.initialized', '1\n', { flag: 'wx', mode: 0o600 });
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 => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[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>Gateway · 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>Access control</h1>
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' ? 'Managed by ingress' : state.mode === 'oauth' ? 'OAuth' : 'Gateway API key'}</h2><p>Permission: Owner default. Credentials do not change workspace, tool or operating-system permissions.</p><p>${state.mode === 'ingress' ? 'Gateway does not verify caller identity. Restrict remote access through your ingress. HTTPS encryption alone does not authenticate callers.' : 'Every MCP request requires an active credential. Missing, invalid, expired or revoked credentials are rejected.'}</p></section>
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:&lt;PORT&gt;/mcp</code></td></tr>
16
+ <tr><td>URL (Remote)</td><td><code>${state.mode === 'oauth' && state.oauth ? e(state.oauth.resource) : 'https://&lt;your-domain-or-tunnel&gt;/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 &lt;YOUR_API_KEY&gt;' : `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>