Repo-side hardening and tooling from the intensive test round (none of this ships in the app installer). - gateway: registry.json (holds bearer tokens) now gets a best-effort owner-only NTFS ACL on Windows via `icacls /inheritance:r /grant:r <user>:F` (the chmod 0600 is a no-op on NTFS); verified the file ends up <user>:(F) only. - gateway: connect_server now reads the app version from the real get_system_info shape (data.app.version / data.agent.version), so "connected to vX.Y.Z" works. - gateway: read_log tool description documents grep as a case-insensitive substring filter with "|" alternation (not a regex), matching the agent-side change. - gateway: standalone verification harnesses moved to gateway/verify/ (so `node --test` only sweeps real unit tests) and exposed via `npm run verify`: e2e-verify, integration-mcp (live gateway-MCP <-> agent, all 14 tools), and adversarial-probe (redaction fuzz + ReDoS + lockout). `npm test` runs the units. - eslint: gateway/** now lints as ESM (sourceType module) via a dedicated block; global ignores fixed so `eslint .` is clean across the whole project (0 errors). - docs/remote-diagnostics-setup.md: made the transport story honest — the agent speaks plaintext ws:// over enforced loopback; the SSH/WireGuard tunnel is the ONLY confidentiality layer (wss/TLS + cert-pin is a documented future mode, not active). Removed the stale "bind to a LAN/VPN IP" guidance (loopback is enforced). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
134 lines
5.4 KiB
Markdown
134 lines
5.4 KiB
Markdown
# Remote Diagnostics Setup (read-only)
|
|
|
|
This guide explains how to let Claude Code run **read-only** diagnostics against a
|
|
Multi-Hoster-Uploader instance running on a remote Windows server, through the local
|
|
**stdio MCP gateway** in `gateway/`.
|
|
|
|
The gateway is an MCP server to Claude and a WebSocket client to a diagnostic agent inside the
|
|
app. It never touches the screen, never injects input, and never writes anything on the server.
|
|
|
|
---
|
|
|
|
## 1. Enable diagnostics on the server and copy the code
|
|
|
|
On the **server** (the machine running the app):
|
|
|
|
1. Open the app's **Settings**.
|
|
2. Enable **"Diagnose-Zugriff"**.
|
|
3. Copy the connection **code**. It looks like `mhu1_<base64url...>`.
|
|
|
|
The code carries a one-time auth **token**. It does **not** carry a host — you supply the host
|
|
yourself when you connect. Treat the code as a **secret**: anyone with the code and network reach
|
|
to the agent can read diagnostics.
|
|
|
|
The agent **always binds to `127.0.0.1`** (loopback only) — this is enforced; there is no setting
|
|
to expose it on a LAN/Internet interface. You reach it through a tunnel (next step).
|
|
|
|
**Transport note:** the agent speaks **plaintext `ws://` over loopback**. The token and the
|
|
diagnostic data are *not* encrypted on the wire by the agent itself — confidentiality comes
|
|
entirely from the **tunnel** (SSH/WireGuard) you put in front of it. The loopback bind means the
|
|
plaintext traffic never leaves the host except inside that encrypted tunnel. (A future build may
|
|
add `wss`/TLS with cert pinning via an `fp` field in the code; it is not active today.)
|
|
|
|
---
|
|
|
|
## 2. Reach the agent over a tunnel (SSH local port-forward or WireGuard)
|
|
|
|
Because the agent binds to `127.0.0.1` on the server, open a tunnel from your machine to the
|
|
server's loopback. The agent's default port is `9110`.
|
|
|
|
### SSH local port-forward (recommended)
|
|
|
|
```
|
|
ssh -L 9110:127.0.0.1:9110 user@server
|
|
```
|
|
|
|
Leave that session open. Now `127.0.0.1:9110` on **your** machine is forwarded to
|
|
`127.0.0.1:9110` on the **server**. You connect Claude to **`127.0.0.1`** (your local end of the
|
|
tunnel), not the server's public IP.
|
|
|
|
### WireGuard (alternative)
|
|
|
|
Bring up a WireGuard tunnel to the server, then connect to the server's WireGuard address (or, if
|
|
you forward loopback over the tunnel, `127.0.0.1`). Use whichever address resolves to the agent's
|
|
`127.0.0.1:9110` on the server.
|
|
|
|
> The agent cannot be bound directly to a LAN/Internet IP in this build (loopback is enforced),
|
|
> so the tunnel is the **only** way to reach it remotely — and the only thing encrypting the
|
|
> transport. Keep the tunnel (SSH/WireGuard) up for the whole session.
|
|
|
|
---
|
|
|
|
## 3. Install the gateway and register it with Claude (one time)
|
|
|
|
On the machine running Claude Code:
|
|
|
|
```
|
|
cd gateway
|
|
npm install
|
|
```
|
|
|
|
Register the gateway as an MCP server (one time):
|
|
|
|
```
|
|
claude mcp add --transport stdio mhu-diag -- node "C:\Users\ploet\Desktop\Claude Projekte\multi-hoster-uploader\gateway\index.js"
|
|
```
|
|
|
|
Adjust the absolute path if your checkout lives elsewhere.
|
|
|
|
---
|
|
|
|
## 4. Usage
|
|
|
|
With the tunnel up, tell Claude:
|
|
|
|
```
|
|
server prod-3 at 127.0.0.1, code mhu1_<...>
|
|
```
|
|
|
|
Claude will:
|
|
|
|
1. call `connect_server(code:"mhu1_...", host:"127.0.0.1")`,
|
|
2. immediately read the app version via `get_system_info`,
|
|
3. remember the server under its label in `registry.json`.
|
|
|
|
Next time you can just say:
|
|
|
|
```
|
|
connect_server(label:"prod-3")
|
|
```
|
|
|
|
with no code — the gateway dials it from the registry. Then ask Claude "what's wrong?" — it will
|
|
typically start with **`server_health`**, the one-shot hub that aggregates errors, queue, rotation
|
|
and system info in a single call. Other read-only tools: `read_log`, `list_logs`, `list_errors`,
|
|
`get_queue_state`, `get_history`, `get_config_redacted`, `get_rotation_state`, `get_app_events`.
|
|
|
|
---
|
|
|
|
## 5. Failure → meaning
|
|
|
|
When a connect or a request fails, the gateway returns a human-readable cause. Quick reference:
|
|
|
|
| Symptom / signal | What it means / what to do |
|
|
| -------------------------------------- | ----------------------------------------------------------------------- |
|
|
| `ECONNREFUSED` | app not running / wrong port / inbound firewall closed |
|
|
| `ETIMEDOUT` | firewall DROP / NAT not forwarded / tunnel down |
|
|
| close code **4001** | auth timeout, retry |
|
|
| close code **4002** | stale or rotated code — re-copy the current code from the server |
|
|
| close code **4003** | brute-force lockout, wait 60s |
|
|
| connected but no `auth-ok` | old app version without the diagnostic agent — update that server |
|
|
| wss fingerprint mismatch | reserved for the future TLS mode (not active today) — re-copy the code |
|
|
|
|
If you tunnel and still get `ECONNREFUSED`, check that the SSH session is up and that the agent is
|
|
actually listening on `127.0.0.1:9110` on the server (Diagnose-Zugriff enabled).
|
|
|
|
---
|
|
|
|
## 6. What this is — and is not
|
|
|
|
- It is **READ-ONLY**. It reads logs, errors, queue/history, redacted config, rotation and system
|
|
info. **No screen capture. No input injection. No writes or config changes.**
|
|
- The **code is a secret** (it carries the auth token). Don't paste it into public channels.
|
|
- `gateway/registry.json` stores tokens for remembered servers and is **git-ignored** — never
|
|
commit it.
|