mirror of
https://github.com/daggerhashimoto/openclaw-nerve
synced 2026-04-21 10:37:17 +00:00
Refresh docs for current gateway auth flow
This commit is contained in:
parent
9f79217e0d
commit
c8cf655659
9 changed files with 77 additions and 59 deletions
38
docs/API.md
38
docs/API.md
|
|
@ -180,30 +180,40 @@ Returns the application name and version from `package.json`.
|
|||
|
||||
### `GET /api/connect-defaults`
|
||||
|
||||
Provides gateway WebSocket URL and auth token for the frontend's auto-connect feature. **The gateway token is only returned to loopback clients** — remote clients receive `null`.
|
||||
Provides the official gateway WebSocket URL and trust metadata for the frontend's auto-connect flow. The `token` field is always `null`; when `serverSideAuth` is `true`, Nerve expects the browser to connect with an empty token and injects `GATEWAY_TOKEN` server-side during the WebSocket handshake.
|
||||
|
||||
**Rate Limit:** None
|
||||
**Rate Limit:** General (`60 requests / minute`)
|
||||
|
||||
**Response (loopback):**
|
||||
|
||||
```json
|
||||
{
|
||||
"wsUrl": "ws://127.0.0.1:18789/ws",
|
||||
"token": "your-gateway-token",
|
||||
"agentName": "Agent"
|
||||
}
|
||||
```
|
||||
|
||||
**Response (remote):**
|
||||
**Response (trusted / auto-connect path):**
|
||||
|
||||
```json
|
||||
{
|
||||
"wsUrl": "ws://127.0.0.1:18789/ws",
|
||||
"token": null,
|
||||
"agentName": "Agent"
|
||||
"agentName": "Agent",
|
||||
"authEnabled": false,
|
||||
"serverSideAuth": true
|
||||
}
|
||||
```
|
||||
|
||||
**Response (manual token still required):**
|
||||
|
||||
```json
|
||||
{
|
||||
"wsUrl": "ws://127.0.0.1:18789/ws",
|
||||
"token": null,
|
||||
"agentName": "Agent",
|
||||
"authEnabled": true,
|
||||
"serverSideAuth": false
|
||||
}
|
||||
```
|
||||
|
||||
`serverSideAuth` becomes `true` when Nerve can safely inject the configured gateway token for this request, such as:
|
||||
- loopback / tunneled local access to the official gateway URL
|
||||
- authenticated sessions on a network-exposed Nerve instance
|
||||
|
||||
If the browser is pointed at a custom gateway URL, or the request is not trusted for server-side injection, the connect dialog keeps the token field visible and the user must supply it manually.
|
||||
|
||||
---
|
||||
|
||||
## Events (SSE)
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@ Cmd+K command palette.
|
|||
#### `features/connect/`
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `ConnectDialog.tsx` | Initial gateway connection dialog with auto-connect from `/api/connect-defaults` |
|
||||
| `ConnectDialog.tsx` | Initial gateway connection dialog. Uses official gateway URL + `serverSideAuth` metadata from `/api/connect-defaults` to decide whether the token field is needed |
|
||||
|
||||
#### `features/activity/`
|
||||
| File | Purpose |
|
||||
|
|
@ -260,7 +260,7 @@ Cmd+K command palette.
|
|||
| Hook | File | Purpose |
|
||||
|------|------|---------|
|
||||
| `useWebSocket` | `hooks/useWebSocket.ts` | Core WebSocket management — connect, RPC, auto-reconnect with exponential backoff |
|
||||
| `useConnectionManager` | `hooks/useConnectionManager.ts` | Auto-connect logic, credential persistence in `sessionStorage` |
|
||||
| `useConnectionManager` | `hooks/useConnectionManager.ts` | Auto-connect logic, official gateway resolution, `serverSideAuth` gating, and credential persistence in `localStorage` |
|
||||
| `useDashboardData` | `hooks/useDashboardData.ts` | Fetches memories and token data via REST + SSE |
|
||||
| `useServerEvents` | `hooks/useServerEvents.ts` | SSE client for `/api/events` |
|
||||
| `useInputHistory` | `hooks/useInputHistory.ts` | Up/down arrow input history |
|
||||
|
|
@ -319,7 +319,7 @@ Applied in order in `app.ts`:
|
|||
| `/api/auth/status` | `routes/auth.ts` | GET | Check whether auth is enabled and current session validity |
|
||||
| `/api/auth/login` | `routes/auth.ts` | POST | Authenticate with password, set signed session cookie |
|
||||
| `/api/auth/logout` | `routes/auth.ts` | POST | Clear session cookie |
|
||||
| `/api/connect-defaults` | `routes/connect-defaults.ts` | GET | Pre-fill gateway URL/token for browser. Token only returned for loopback clients |
|
||||
| `/api/connect-defaults` | `routes/connect-defaults.ts` | GET | Returns the official gateway WS URL plus `authEnabled` / `serverSideAuth` metadata. `token` is always `null`; trusted flows use server-side token injection |
|
||||
| `/api/events` | `routes/events.ts` | GET, POST | SSE stream for real-time push (memory.changed, tokens.updated, status.changed, ping). POST for test events |
|
||||
| `/api/tts` | `routes/tts.ts` | POST | Text-to-speech with provider auto-selection (OpenAI → Replicate → Edge). LRU cache with TTL |
|
||||
| `/api/tts/config` | `routes/tts.ts` | GET, PUT | TTS voice configuration per provider (read / partial update) |
|
||||
|
|
|
|||
|
|
@ -228,12 +228,12 @@ const fetchLimits = createCachedFetch(
|
|||
- **Authentication:** Session-cookie auth via `middleware/auth.ts`. When enabled, all `/api/*` routes (except auth/health) require a valid HMAC-SHA256 signed cookie. WebSocket upgrades checked in `ws-proxy.ts`
|
||||
- **Session tokens:** Stateless signed cookies (`HttpOnly`, `SameSite=Strict`). Password hashing via scrypt. Gateway token accepted as fallback password
|
||||
- **CORS:** Strict origin allowlist — only localhost variants and explicitly configured origins
|
||||
- **Token exposure:** Gateway token only returned to loopback clients (`/api/connect-defaults`)
|
||||
- **Token exposure:** Managed gateway auth uses server-side token injection. `/api/connect-defaults` returns `token: null` and trust metadata instead of the raw gateway token
|
||||
- **Device identity:** Ed25519 keypair for gateway WS auth (`~/.nerve/device-identity.json`). Required for operator scopes on OpenClaw 2026.2.19+
|
||||
- **File serving:** MIME-type allowlist + directory traversal prevention + allowed prefix check
|
||||
- **Body limits:** Configurable per-route (general API vs transcribe uploads)
|
||||
- **Rate limiting:** Per-IP sliding window with separate limits for expensive operations
|
||||
- **Credentials:** `sessionStorage` (not `localStorage`) for gateway auth — cleared on tab close
|
||||
- **Credentials:** Browser connection config persists in `localStorage` as `oc-config`. Official managed gateway flows can keep the token empty; custom manual tokens may persist until cleared
|
||||
- **Input validation:** Zod schemas on all POST/PUT request bodies
|
||||
|
||||
### Graceful Shutdown
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ The wizard backs up existing `.env` files (e.g. `.env.bak.1708100000000`) before
|
|||
|
||||
> **⚠️ Network exposure:** Setting `HOST=0.0.0.0` exposes all endpoints to the network. Enable authentication (`NERVE_AUTH=true`) and set a password via the setup wizard before binding to a non-loopback address. Without auth, anyone with network access can read/write agent memory, modify config files, and control sessions. See [Security](SECURITY.md) for the full threat model.
|
||||
|
||||
```env
|
||||
```bash
|
||||
PORT=3080
|
||||
SSL_PORT=3443
|
||||
HOST=127.0.0.1
|
||||
|
|
@ -98,7 +98,7 @@ HOST=127.0.0.1
|
|||
| `GATEWAY_TOKEN` | — | **Yes** | Authentication token for the OpenClaw gateway. The setup wizard auto-detects this. See note below |
|
||||
| `GATEWAY_URL` | `http://127.0.0.1:18789` | No | Gateway HTTP endpoint URL |
|
||||
|
||||
```env
|
||||
```bash
|
||||
GATEWAY_TOKEN=your-token-here
|
||||
GATEWAY_URL=http://127.0.0.1:18789
|
||||
```
|
||||
|
|
@ -123,7 +123,7 @@ This allows the browser UI to connect without having to manually enter or store
|
|||
|----------|---------|-------------|
|
||||
| `AGENT_NAME` | `Agent` | Display name shown in the UI header and server info |
|
||||
|
||||
```env
|
||||
```bash
|
||||
AGENT_NAME=Friday
|
||||
```
|
||||
|
||||
|
|
@ -134,7 +134,7 @@ AGENT_NAME=Friday
|
|||
| `OPENAI_API_KEY` | Enables OpenAI TTS (multiple voices) and Whisper audio transcription |
|
||||
| `REPLICATE_API_TOKEN` | Enables Replicate-hosted TTS models (e.g. Qwen TTS). Requires `ffmpeg` for WAV→MP3 |
|
||||
|
||||
```env
|
||||
```bash
|
||||
OPENAI_API_KEY=sk-...
|
||||
REPLICATE_API_TOKEN=r8_...
|
||||
```
|
||||
|
|
@ -154,7 +154,7 @@ TTS provider fallback chain (when no explicit provider is requested):
|
|||
| `NERVE_LANGUAGE` | `en` | Preferred voice language (ISO 639-1). Legacy `LANGUAGE` is still accepted but deprecated |
|
||||
| `EDGE_VOICE_GENDER` | `female` | Edge TTS voice gender: `female` or `male` |
|
||||
|
||||
```env
|
||||
```bash
|
||||
# Use local speech-to-text (no API key needed)
|
||||
STT_PROVIDER=local
|
||||
WHISPER_MODEL=tiny
|
||||
|
|
@ -178,7 +178,7 @@ Voice phrase overrides (stop/cancel/wake words) are stored at `~/.nerve/voice-ph
|
|||
| `WS_ALLOWED_HOSTS` | `localhost,127.0.0.1,::1` | Additional WebSocket proxy allowed hostnames, comma-separated |
|
||||
| `TRUSTED_PROXIES` | `127.0.0.1,::1,::ffff:127.0.0.1` | IP addresses trusted to set `X-Forwarded-For` / `X-Real-IP` headers, comma-separated |
|
||||
|
||||
```env
|
||||
```bash
|
||||
# Tailscale example
|
||||
ALLOWED_ORIGINS=http://100.64.0.5:3080
|
||||
CSP_CONNECT_EXTRA=http://100.64.0.5:3080 ws://100.64.0.5:3080
|
||||
|
|
@ -199,7 +199,7 @@ Nerve includes a built-in authentication layer that protects all API endpoints,
|
|||
| `NERVE_SESSION_SECRET` | *(auto-generated)* | 32-byte hex string for HMAC-SHA256 cookie signing. Auto-generated during setup. If not set, an ephemeral secret is generated at startup (sessions won't survive restarts) |
|
||||
| `NERVE_SESSION_TTL` | `2592000000` (30 days) | Session lifetime in milliseconds |
|
||||
|
||||
```env
|
||||
```bash
|
||||
NERVE_AUTH=true
|
||||
NERVE_PASSWORD_HASH=<generated-by-setup>
|
||||
NERVE_SESSION_SECRET=<generated-by-setup>
|
||||
|
|
@ -209,13 +209,13 @@ NERVE_SESSION_SECRET=<generated-by-setup>
|
|||
|
||||
When `HOST=0.0.0.0` and `NERVE_AUTH=false`, the server **refuses to start** to prevent accidentally exposing all endpoints without authentication. Set `NERVE_ALLOW_INSECURE=true` to override this safety check. **Not recommended for production.**
|
||||
|
||||
```env
|
||||
```bash
|
||||
NERVE_ALLOW_INSECURE=true
|
||||
```
|
||||
|
||||
**Quick enable (with gateway token as password):**
|
||||
|
||||
```env
|
||||
```bash
|
||||
NERVE_AUTH=true
|
||||
NERVE_SESSION_SECRET=$(openssl rand -hex 32)
|
||||
# No NERVE_PASSWORD_HASH needed — your GATEWAY_TOKEN works as the password
|
||||
|
|
@ -239,7 +239,7 @@ Override these for proxies, self-hosted endpoints, or API-compatible alternative
|
|||
| `OPENAI_BASE_URL` | `https://api.openai.com/v1` | OpenAI-compatible API base URL |
|
||||
| `REPLICATE_BASE_URL` | `https://api.replicate.com/v1` | Replicate API base URL |
|
||||
|
||||
```env
|
||||
```bash
|
||||
OPENAI_BASE_URL=https://api.openai.com/v1
|
||||
REPLICATE_BASE_URL=https://api.replicate.com/v1
|
||||
```
|
||||
|
|
@ -263,7 +263,7 @@ REPLICATE_BASE_URL=https://api.replicate.com/v1
|
|||
| `NERVE_WATCH_WORKSPACE_RECURSIVE` | `false` | Enables recursive `fs.watch` for the entire workspace (legacy behavior). Disabled by default to prevent Linux inotify `ENOSPC` watcher exhaustion. |
|
||||
| `WORKSPACE_ROOT` | *(auto-detected)* | Allowed base directory for git workdir registration. Auto-derived from `git worktree list` or parent of `process.cwd()` |
|
||||
|
||||
```env
|
||||
```bash
|
||||
FILE_BROWSER_ROOT=/home/user
|
||||
MEMORY_PATH=/custom/path/MEMORY.md
|
||||
MEMORY_DIR=/custom/path/memory/
|
||||
|
|
@ -279,7 +279,7 @@ NERVE_WATCH_WORKSPACE_RECURSIVE=false
|
|||
| `TTS_CACHE_TTL_MS` | `3600000` (1 hour) | Time-to-live for cached TTS audio in milliseconds |
|
||||
| `TTS_CACHE_MAX` | `200` | Maximum number of cached TTS entries (in-memory LRU) |
|
||||
|
||||
```env
|
||||
```bash
|
||||
TTS_CACHE_TTL_MS=7200000
|
||||
TTS_CACHE_MAX=500
|
||||
```
|
||||
|
|
@ -382,7 +382,7 @@ Or use the setup wizard's Custom access mode, which generates them automatically
|
|||
|
||||
## Minimal `.env` Example
|
||||
|
||||
```env
|
||||
```bash
|
||||
GATEWAY_TOKEN=abc123def456
|
||||
```
|
||||
|
||||
|
|
@ -390,7 +390,7 @@ Everything else uses defaults. This is sufficient for local-only usage.
|
|||
|
||||
## Full `.env` Example
|
||||
|
||||
```env
|
||||
```bash
|
||||
# Gateway (required)
|
||||
GATEWAY_TOKEN=abc123def456
|
||||
GATEWAY_URL=http://127.0.0.1:18789
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ openclaw devices approve <requestId>
|
|||
|
||||
### Browser keeps old credentials
|
||||
|
||||
**Fix:** Open a new tab or private window. Nerve stores the gateway token in `sessionStorage`, which clears when the tab closes.
|
||||
**Fix:** Clear site data or remove `localStorage.oc-config`. Nerve stores the gateway URL and any manually-entered token there for reconnects, so a stale manual token can override the official managed connection path.
|
||||
|
||||
## Security notes
|
||||
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ When prompted:
|
|||
|
||||
If your gateway hostname isn't localhost, add it to `.env`:
|
||||
|
||||
```env
|
||||
```bash
|
||||
WS_ALLOWED_HOSTS=<gateway-hostname-or-ip>
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ Follow the same-host steps for Nerve, then add:
|
|||
|
||||
In `.env`:
|
||||
|
||||
```env
|
||||
```bash
|
||||
GATEWAY_URL=<remote-gateway-url>
|
||||
WS_ALLOWED_HOSTS=<remote-gateway-hostname-or-ip>
|
||||
```
|
||||
|
|
@ -115,9 +115,11 @@ In the browser: login screen appears, connect succeeds, sessions load, messages
|
|||
|
||||
## Common issues
|
||||
|
||||
### Remote clients don't get auto token prefill
|
||||
### Remote clients may still need manual credentials
|
||||
|
||||
`/api/connect-defaults` only returns the token to loopback clients. Remote users must enter the gateway token manually in the connect dialog.
|
||||
Remote clients can still auto-connect when Nerve trusts the request and the browser is using the official gateway URL. In that case `/api/connect-defaults` reports `serverSideAuth=true`, the browser sends an empty token, and Nerve injects `GATEWAY_TOKEN` server-side during the WebSocket handshake.
|
||||
|
||||
Manual token entry is only required for custom gateway URLs or untrusted access paths.
|
||||
|
||||
### Reverse proxy and trusted proxy settings
|
||||
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ Security is enforced through network-level controls:
|
|||
1. **Localhost binding** — The server binds to `127.0.0.1` by default. Only local processes can connect.
|
||||
2. **CORS allowlist** — Browsers enforce the Origin check. Only configured origins receive CORS headers.
|
||||
3. **Gateway token isolation** — The sensitive `GATEWAY_TOKEN` is never sent to the browser. Instead, Nerve injects it server-side into the WebSocket connection upgrade for trusted clients.
|
||||
4. **Session storage** — The frontend stores the gateway token in `sessionStorage` (cleared when the tab closes), not `localStorage`.
|
||||
4. **Client config persistence** — The frontend stores the gateway URL and optional manual token in `localStorage` as `oc-config`. Trusted official-gateway flows usually keep the token empty because server-side injection handles auth.
|
||||
|
||||
### When Auth is Enabled
|
||||
|
||||
|
|
@ -118,7 +118,7 @@ CORS is enforced on all requests via Hono's CORS middleware.
|
|||
|
||||
**Additional origins** via `ALLOWED_ORIGINS` env var (comma-separated). Each entry is normalised through the `URL` constructor:
|
||||
|
||||
```env
|
||||
```bash
|
||||
ALLOWED_ORIGINS=http://100.64.0.5:3080,https://my-server.tailnet.ts.net:3443
|
||||
```
|
||||
|
||||
|
|
@ -287,7 +287,7 @@ The WebSocket proxy (connecting the frontend to the OpenClaw gateway) restricts
|
|||
|
||||
**Extend via** `WS_ALLOWED_HOSTS` env var (comma-separated):
|
||||
|
||||
```env
|
||||
```bash
|
||||
WS_ALLOWED_HOSTS=my-server.tailnet.ts.net,100.64.0.5
|
||||
```
|
||||
|
||||
|
|
@ -298,13 +298,15 @@ This prevents the proxy from being used to connect to arbitrary external hosts.
|
|||
Nerve performs **server-side token injection** to provide a zero-config connection experience for local and authenticated users without exposing the `GATEWAY_TOKEN` to the browser storage.
|
||||
|
||||
**Injection Logic:**
|
||||
1. The WebSocket proxy identifies if a connection is **trusted**.
|
||||
- **Local Trusted**: The client IP (resolved via `TRUSTED_PROXIES` if applicable) is a loopback address (`127.0.0.1` / `::1`).
|
||||
- **Session Trusted**: The request carries a valid session cookie (`NERVE_AUTH=true`).
|
||||
2. If trusted and a `GATEWAY_TOKEN` is configured, Nerve intercepts the client's `connect` request.
|
||||
3. If the client did not provide a token, Nerve injects the server's `GATEWAY_TOKEN`.
|
||||
1. `GET /api/connect-defaults` returns the official gateway WebSocket URL, `token: null`, and a `serverSideAuth` flag.
|
||||
2. The WebSocket proxy only injects `GATEWAY_TOKEN` when all of these are true:
|
||||
- a gateway token is configured on the server
|
||||
- the request is trusted (`loopback` access or an authenticated session)
|
||||
- the WebSocket upgrade `Origin` is allowed
|
||||
3. For the official gateway URL, the browser connects with an empty token when `serverSideAuth=true`.
|
||||
4. Custom gateway URLs or untrusted contexts still require manual token entry in the connect dialog.
|
||||
|
||||
This allows the UI to hide the "Auth Token" field and auto-connect for trusted users while keeping the token strictly on the server.
|
||||
This keeps the managed gateway token on the server while still allowing explicit manual credentials for unsupported or custom connection paths.
|
||||
|
||||
### Device Identity & Gateway Scopes
|
||||
|
||||
|
|
@ -374,10 +376,10 @@ HSTS is always sent (`max-age=31536000; includeSubDomains`), even over HTTP. Bro
|
|||
|
||||
| Secret | Storage | Exposure |
|
||||
|--------|---------|----------|
|
||||
| `GATEWAY_TOKEN` | `.env` file (chmod 600) | Only returned to loopback clients via `/api/connect-defaults`. Never logged. |
|
||||
| `GATEWAY_TOKEN` | `.env` file (chmod 600) | Used server-side for trusted official-gateway connections. `/api/connect-defaults` returns `token: null`. Never logged. |
|
||||
| `OPENAI_API_KEY` | `.env` file | Used server-side only. Never sent to clients. |
|
||||
| `REPLICATE_API_TOKEN` | `.env` file | Used server-side only. Never sent to clients. |
|
||||
| Gateway token (client) | `sessionStorage` | Cleared when browser tab closes. Not persisted to disk. |
|
||||
| Gateway URL + optional manual token | `localStorage` (`oc-config`) | Used for reconnects. Trusted official-gateway flows usually keep the token empty; manually entered custom-gateway tokens persist until cleared. |
|
||||
|
||||
The setup wizard applies `chmod 600` to `.env` and backup files, restricting read access to the file owner.
|
||||
|
||||
|
|
@ -388,7 +390,7 @@ The setup wizard applies `chmod 600` to `.env` and backup files, restricting rea
|
|||
| Measure | Details |
|
||||
|---------|---------|
|
||||
| **DOMPurify** | All rendered HTML (agent messages, markdown) passes through DOMPurify with a strict tag/attribute allowlist |
|
||||
| **Session storage** | Gateway token stored in `sessionStorage`, not `localStorage` — cleared on tab close |
|
||||
| **Local storage** | Connection preferences are stored in `localStorage` as `oc-config`. The official managed gateway path can keep the token empty; manually entered custom tokens may persist until cleared |
|
||||
| **CSP enforcement** | `script-src 'self' https://s3.tradingview.com` blocks inline scripts and limits external scripts to TradingView chart widgets only |
|
||||
| **No eval** | No use of `eval()`, `Function()`, or `innerHTML` with unsanitised content |
|
||||
|
||||
|
|
|
|||
|
|
@ -122,8 +122,8 @@ The server detects `EADDRINUSE` and exits with a clear error (see `server/index.
|
|||
**Fix:**
|
||||
- Verify the gateway is running: `openclaw gateway status`
|
||||
- Check token: the server reads `GATEWAY_TOKEN` or `OPENCLAW_GATEWAY_TOKEN` env var
|
||||
- For local access, `/api/connect-defaults` auto-provides the token (loopback only)
|
||||
- For remote access, the token is NOT auto-provided (security). Enter it manually in the connection dialog
|
||||
- For trusted official-gateway access, `/api/connect-defaults` advertises `serverSideAuth=true` and Nerve connects with an empty browser-side token
|
||||
- For custom gateway URLs or untrusted access, enter the token manually in the connection dialog
|
||||
|
||||
### Connection drops and "SIGNAL LOST" banner
|
||||
|
||||
|
|
@ -144,17 +144,21 @@ curl http://127.0.0.1:3080/health
|
|||
**Fix:**
|
||||
- If gateway is unreachable, restart it: `openclaw gateway restart`
|
||||
- If persistent, check firewall rules or network configuration
|
||||
- The client stores credentials in `sessionStorage` (cleared on tab close) — if credentials are lost, reconnect manually
|
||||
- If a stale manual token is saved in `localStorage`, clear `oc-config` and reload before reconnecting
|
||||
|
||||
### Auto-connect doesn't work
|
||||
|
||||
**Symptom:** ConnectDialog appears even though the gateway is running.
|
||||
|
||||
**Cause:** The frontend fetches `/api/connect-defaults` on mount. This endpoint only returns the token for loopback clients (127.0.0.1, ::1).
|
||||
**Cause:** The frontend fetches `/api/connect-defaults` on mount, but auto-connect only happens when:
|
||||
- `serverSideAuth=true`
|
||||
- the saved gateway URL is empty or matches the server's official gateway URL
|
||||
- the initial connect attempt succeeds
|
||||
|
||||
**Fix:**
|
||||
- If accessing Nerve remotely (SSH tunnel, reverse proxy), you must enter the gateway URL and token manually
|
||||
- Alternatively, set the gateway URL in the connection dialog — the server's WebSocket proxy handles the actual connection
|
||||
- If you want the managed path, clear stale browser config (`localStorage.oc-config`) and reload
|
||||
- If you are using a custom gateway URL, manual token entry is expected
|
||||
- If you are remote but authenticated and using the official gateway URL, the dialog should not be required after stale config is cleared
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -193,11 +197,11 @@ WS_ALLOWED_HOSTS=mygateway.local npm start
|
|||
**Symptom:** Server logs show `[ws-proxy] Gateway closed: code=1008, reason=unauthorized: device token mismatch`.
|
||||
|
||||
**Causes:**
|
||||
1. **Stale browser token.** The browser caches the gateway token in `sessionStorage`. If the token changes (e.g., after re-running setup or restarting the gateway), the browser still sends the old one.
|
||||
1. **Stale browser config.** The browser may still have an old manually-entered token saved in `localStorage` (`oc-config`), often from an older build or a custom gateway connection.
|
||||
2. **Token mismatch across config files.** OpenClaw 2026.2.19 has a known bug where `openclaw onboard` writes different tokens to the systemd service file and `openclaw.json`. The gateway uses the systemd env var; Nerve reads from `.env`.
|
||||
|
||||
**Fix (stale browser):**
|
||||
Close the tab completely and open a fresh one (or use incognito). `sessionStorage` is cleared on tab close.
|
||||
**Fix (stale browser config):**
|
||||
Clear site data or remove `localStorage.oc-config`, then reload so the official managed gateway path can reconnect with an empty token.
|
||||
|
||||
**Fix (token mismatch):**
|
||||
Re-run the setup wizard — it reads the real token from the systemd service file and aligns everything:
|
||||
|
|
|
|||
Loading…
Reference in a new issue