squad-openclaw 2026.2.2702 → 2026.2.2703

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 (3) hide show
  1. package/README.md +56 -111
  2. package/dist/index.js +563 -1612
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # squad-openclaw
2
2
 
3
- OpenClaw gateway plugin for [Squad](https://squad.ceo) — provides entity registry, filesystem tools, SQL queries, version management, and a cloud relay client for remote browser access.
3
+ OpenClaw gateway plugin for [Squad](https://squad.ceo) — provides entity registry, filesystem tools, SQL queries, version management, and Tailnet internal routes for secure onboarding helpers.
4
4
 
5
5
  ## Features
6
6
 
@@ -10,9 +10,9 @@ OpenClaw gateway plugin for [Squad](https://squad.ceo) — provides entity regis
10
10
  | `fs_read`, `fs_write`, `fs_list`, `fs_delete`, `fs_rename`, `fs_mkdir` | Remote filesystem access for browser clients (subject to security restrictions below) |
11
11
  | `sql_query` | Restricted SQLite query tool — `sqlite3` only, scoped to `~/.openclaw/squad-ceo-data/` |
12
12
  | `squad.version.check`, `squad.version.update` | Plugin version management and self-update |
13
- | `squad.agents.set-identity`, `squad.agents.patch-config` | Gateway-native, surgical agent config updates (identity/tools/skills/default/model only) |
14
- | `tools.invoke` | RPC-based tool invocation for relay mode — **only invokes this plugin's own tools**, each with its own security restrictions (see below) |
15
- | Cloud relay client | Connects outbound to `relay.squad.ceo` for remote browser access. **Only activates when a claim token or room ID exists** (see Relay Security below) |
13
+ | `tools.invoke` | RPC-based tool invocation over gateway methods — **only invokes this plugin's own tools**, each with its own security restrictions (see below) |
14
+ | `squad.plugin.status`, `squad.plugin.recover`, `squad.plugin.disable` | Plugin safety-state RPC control (status, recovery, manual disable) |
15
+ | `/squad-internal/health`, `/squad-internal/plugin/*`, `/squad-internal/pairing/*` | Tailnet internal health, safety-state, and pairing routes with origin/Tailnet checks |
16
16
 
17
17
  ## State Directory Resolution
18
18
 
@@ -78,7 +78,7 @@ These directories are **completely blocked** from all filesystem operations (rea
78
78
 
79
79
  | Path | Reason |
80
80
  |---|---|
81
- | `~/.openclaw/squad-ceo-data/relay/squad-relay.json` | Contains ed25519 private key for relay device identity |
81
+ | `~/.openclaw/squad-ceo-data/relay/squad-relay.json` | Legacy relay-state material from older transport flows (blocked to prevent accidental secret disclosure) |
82
82
  | `~/.openclaw/*.bak` | Backup files at the top level contain unredacted config (tokens, keys) that would bypass redaction |
83
83
 
84
84
  ### Layer 2: Redacted Files (hardcoded, non-configurable)
@@ -107,17 +107,10 @@ Operators can customize via the `fs.allowedRoots` config option.
107
107
  These files/directories cannot be written via filesystem tools (`fs_write`, `fs_rename`, etc.), even if they fall within `allowedRoots`:
108
108
 
109
109
  - `~/.openclaw/openclaw.json` — operator configuration (tool-level read-only with redaction)
110
- - `~/.openclaw/squad-ceo-data/relay/squad-relay.json` — relay device private key
110
+ - `~/.openclaw/squad-ceo-data/relay/squad-relay.json` — legacy relay-state secret file (blocked)
111
111
  - All blocked directories above (credentials, devices, identity)
112
112
  - All `.bak` files at `~/.openclaw/` top level
113
113
 
114
- For agent editing UX, this plugin also exposes **whitelisted gateway RPC mutators** that call `config.get` + `config.patch` inside the gateway process (single-writer path):
115
-
116
- - `squad.agents.set-identity` — writes only `agents.list[].identity.{name,emoji,theme}`
117
- - `squad.agents.patch-config` — writes only `agents.list[].{tools,skills,default,model}`
118
-
119
- These methods do **not** permit arbitrary file writes and do not expose credentials. They are intentionally narrow so the UI can update agent metadata safely without direct `openclaw.json` filesystem writes.
120
-
121
114
  ### Startup Config Migration (one-time, localized)
122
115
 
123
116
  On plugin startup, Squad runs an internal migration harness. Current migration behavior:
@@ -133,127 +126,79 @@ On plugin startup, Squad runs an internal migration harness. Current migration b
133
126
 
134
127
  This migration is designed to run once. If an operator later changes these values manually, the plugin does not overwrite them again because the migration is already marked complete.
135
128
 
136
- ## Relay Security
137
-
138
- The cloud relay enables remote browser access to the gateway through `relay.squad.ceo`. This section explains the full architecture for security reviewers.
139
-
140
- ### When Does the Relay Activate?
141
-
142
- The relay client **only activates** when the relay state file (`~/.openclaw/squad-ceo-data/relay/squad-relay.json`) contains a `claimToken` or `roomId`. This file does not exist by default — it is created when the user runs the onboarding prompt from the Squad web app:
143
-
144
- ```bash
145
- mkdir -p ~/.openclaw/squad-ceo-data/relay && \
146
- echo '{"claimToken":"<token>"}' > ~/.openclaw/squad-ceo-data/relay/squad-relay.json
147
- ```
148
-
149
- **If this file does not exist or contains neither key, no relay code runs, no WebSocket is opened, and no connection is made to any external server.** The plugin's entry point (`index.ts`) checks this before calling `startRelayClient()`.
129
+ ## Plugin Safety State (Kill Switch + Quarantine)
150
130
 
151
- The claim token is a short-lived, single-use code generated by the relay server for the authenticated user. It links this gateway to the user's Squad account. Once consumed, the relay returns a `roomId` for future reconnections, and the claim token is no longer needed.
131
+ The plugin persists runtime safety state in:
152
132
 
153
- For local testing, the relay endpoint can be overridden with environment variables:
133
+ - `~/.openclaw/squad-ceo-data/safety/plugin-state.json`
154
134
 
155
- - `OPENCLAW_SQUAD_RELAY_URL` (preferred)
156
- - `SQUAD_RELAY_URL` (fallback)
135
+ State values:
157
136
 
158
- If neither is set, the plugin uses the production default `wss://relay.squad.ceo`.
137
+ - `ACTIVE`
138
+ - `DISABLED_MANUAL`
139
+ - `QUARANTINED_AUTO`
159
140
 
160
- ### How the Relay Connection Works
141
+ Behavior:
161
142
 
162
- ```
163
- ┌──────────────┐ JWT auth ┌──────────────────┐ outbound WS ┌────────────────┐
164
- │ Browser SPA ◄──────────────────────► │ relay.squad.ceo ◄────────────────────────► │ relay-client │
165
- │ (squad.ceo) │ (wss://.../user) │ (CF Worker + DO)│ (wss://.../gw) │ (this plugin) │
166
- └──────────────┘ └──────────────────┘ └───────┬────────┘
167
-
168
- per-user local WS │
169
- (ws://127.0.0.1: │
170
- 18789) │
171
-
172
- ┌──────────────────┐
173
- │ OpenClaw Gateway │
174
- │ (localhost only) │
175
- └──────────────────┘
176
- ```
177
-
178
- 1. **Browser → Relay:** The user authenticates with JWT (email/password or Google OAuth). The browser connects via WebSocket to the relay.
179
- 2. **Relay-client → Relay:** The plugin opens an outbound WebSocket to `relay.squad.ceo` using the claim token (first connect) or stored room ID (reconnect). This is the **only** outbound connection the plugin makes.
180
- 3. **Relay-client → Gateway:** For each browser user, the relay-client opens a **separate local WebSocket** to `ws://127.0.0.1:18789` (the gateway's loopback port). Each user gets an isolated session — the gateway sees them as individual clients.
181
-
182
- ### What Data Crosses the Network (relay.squad.ceo)
183
-
184
- The relay server is a message router. Here is exactly what it sees and does not see:
143
+ - If disabled/quarantined, plugin methods return typed errors (`PLUGIN_DISABLED` / `PLUGIN_QUARANTINED`) instead of crashing startup/runtime.
144
+ - Quarantine auto-expires after a cooldown window and transitions back to `ACTIVE`.
145
+ - Environment kill switch overrides persisted state and is authoritative.
185
146
 
186
- | Data | Crosses relay? | Notes |
187
- |---|---|---|
188
- | Relay protocol envelopes (`relay.forward`, `relay.hello`, etc.) | **Yes** | Routing metadata only |
189
- | User ID (for message routing) | **Yes** | The relay routes by userId |
190
- | Gateway device ID and public key | **Yes** | Sent in `relay.hello` for identification |
191
- | Inner messages (gateway ↔ browser) | **Yes, but opaque** | Wrapped inside `relay.forward` envelopes |
192
- | **Operator auth token** | **NEVER** | Injected into the local WS connect request **after** the relay boundary (see below) |
193
- | **Device identity signature** | **NEVER** | Added to the local WS connect handshake only |
194
- | **Gateway connect handshake params** | **NEVER** | The `connect` request with auth + device identity is sent to `localhost:18789`, not the relay |
195
- | **Plaintext RPC payloads** (when E2E active) | **NEVER** | E2E encrypts before relay; relay sees ciphertext only |
196
-
197
- ### Operator Token — Never Leaves the Server
198
-
199
- The operator token (`gateway.auth.token` in `~/.openclaw/openclaw.json`) is the most sensitive credential. Here is exactly how it flows:
147
+ Environment controls:
200
148
 
201
- 1. **Read:** The relay-client reads the token from `~/.openclaw/openclaw.json` via `fs.readFileSync` at startup. This is equivalent to the gateway reading its own config — the plugin runs in the gateway's process.
149
+ - `SQUAD_PLUGIN_DISABLED=1` force disable plugin
150
+ - `SQUAD_PLUGIN_DISABLE_REASON="..."` — optional operator-facing reason
151
+ - `SQUAD_PLUGIN_FAILURE_THRESHOLD` — failures before auto-quarantine (default `3`)
152
+ - `SQUAD_PLUGIN_FAILURE_WINDOW_MS` — failure counting window (default `300000`)
153
+ - `SQUAD_PLUGIN_QUARANTINE_MS` — quarantine duration (default `600000`)
202
154
 
203
- 2. **Stored:** The token is held in memory (`this.config.operatorToken`) for the lifetime of the relay-client. It is **never written** to any file, log, or external service.
155
+ Recovery paths:
204
156
 
205
- 3. **Used:** When a browser user's `connect` request arrives from the relay, the relay-client **injects** the operator token into the request **in memory**, then sends the modified request to `ws://127.0.0.1:18789` (localhost only). The relay server never sees this injection — it only sees the outer `relay.forward` envelope.
157
+ - RPC: `openclaw gateway call squad.plugin.recover --json`
158
+ - HTTP (Tailnet): `POST /squad-internal/plugin/recover`
159
+ - If env kill switch is active, unset `SQUAD_PLUGIN_DISABLED` and restart gateway.
206
160
 
207
- 4. **Protected via filesystem API:** The token is redacted from `~/.openclaw/openclaw.json` when read through this plugin's `fs_read` tool (`gateway.auth.*` → `"[REDACTED]"`). Remote clients (browser, agents) cannot read the token through any tool this plugin provides.
161
+ ## Tailnet Pairing and Locator Security
208
162
 
209
- ```
210
- Browser ──[connect request]──► relay.squad.ceo ──[relay.forward]──► relay-client
211
-
212
- Token is injected HERE, in memory, │
213
- into the connect request. │
214
-
215
- relay-client ──[modified request]──► localhost:18789 (gateway)
216
- ```
163
+ The active onboarding path is Tailnet-direct and gateway-owned:
217
164
 
218
- **A compromised relay server cannot intercept the operator token** because the token is injected after the relay boundary — it only exists on the `localhost:18789` path between the relay-client and the gateway, both running on the same machine.
165
+ - Browser connects directly to `wss://<gateway>.ts.net`
166
+ - OpenClaw gateway enforces pairing approval state
167
+ - Relay service is used for account auth and locator metadata only
219
168
 
220
- ### Device Identity and Auto-Pairing
169
+ ### Pairing Model (Current)
221
170
 
222
- The relay-client generates an ed25519 keypair on first run (`device-keys.ts`). The device identity is used to sign the `connect` handshake to the local gateway using the v2 signature protocol:
171
+ Pairing does **not** rely on plugin-owned approval state:
223
172
 
224
- - **Signature payload:** `v2|deviceId|clientId|clientMode|role|scopes|signedAtMs|token|nonce` (pipe-delimited)
225
- - **deviceId:** SHA-256 fingerprint of the ed25519 public key (hex, 64 chars)
226
- - **publicKey:** base64url-encoded ed25519 public key (no padding)
173
+ 1. Browser receives gateway `connect.challenge`
174
+ 2. Browser signs challenge with its per-browser device key
175
+ 3. Browser sends native `connect` request
176
+ 4. If unpaired, gateway returns pairing-required error (with `requestId` when available)
177
+ 5. UI shows `openclaw devices approve <requestId>`
178
+ 6. UI polls reconnect automatically in the background until approved
227
179
 
228
- The gateway **auto-pairs** devices that connect with a valid operator token and a correctly signed device identity. No manual `openclaw devices approve` step is required. The authorization chain is:
180
+ This means approval is native to gateway pairing and not stored in plugin-local state.
229
181
 
230
- 1. The **user** authenticates with the Squad web app (JWT via email/password or Google OAuth)
231
- 2. The user generates a **claim token** (short-lived, single-use) and gives it to the gateway via the onboarding prompt
232
- 3. The plugin starts the relay-client, which connects to the relay with the claim token
233
- 4. When a browser user connects, the relay-client signs the connect handshake with its device key and the operator token
234
- 5. The gateway validates the signature and auto-pairs the device
182
+ ### Plugin Route Security (`/squad-internal/pairing/*`)
235
183
 
236
- The claim token is the user's explicit consent to link their gateway to their Squad account. The operator token is the proof of authorization on the gateway side. Together, they form a two-sided trust chain without requiring manual device approval.
184
+ Pairing helper routes enforce:
237
185
 
238
- ### E2E Encryption
186
+ - Allowed browser origin (`SQUAD_ALLOWED_ORIGINS` or built-in defaults)
187
+ - Tailnet context (Tailnet headers/hostname checks)
188
+ - Browser proof (device ID/public key/signature/nonce/signedAt) on pairing request creation
189
+ - Nonce freshness and per-device/IP rate limits
239
190
 
240
- - **Protocol:** ECDH (P-256) key exchange + AES-256-GCM message encryption
241
- - **No plaintext fallback:** If encryption fails after E2E is established, messages are **dropped** — never sent as plaintext. This is enforced in both `routeFromGateway()` and `broadcastToUsers()`.
242
- - **Status:** Implemented in the plugin. Currently disabled at the relay level (Durable Object) due to multi-tab session safety — the relay blocks E2E key exchange to prevent mismatched keys across tabs. When per-session E2E is enabled at the relay, the plugin is already hardened.
191
+ ### Pairing Helper Routes (Compatibility)
243
192
 
244
- ### Authentication Chain Summary
193
+ `/squad-internal/pairing/request` and `/squad-internal/pairing/status` are still present for compatibility, but the current SPA pairing flow no longer depends on them as primary path.
245
194
 
246
- For a browser user's RPC call to reach the gateway through the relay, **all of the following must be valid:**
195
+ ### Legacy Relay-State File Handling
247
196
 
248
- 1. **Browser JWT** user authenticated with relay.squad.ceo
249
- 2. **Relay pairing** — user's account is paired with this gateway's Durable Object (via claim token)
250
- 3. **Relay-client connected** — plugin's outbound WS to relay is alive
251
- 4. **Gateway connect handshake** — relay-client's device identity + operator token accepted by local gateway
252
- 5. **Tool-level security** — each tool enforces its own restrictions (blocked dirs, redaction, write protection, SQL path limits)
197
+ `~/.openclaw/squad-ceo-data/relay/squad-relay.json` is still blocked by filesystem security rules to protect historical secrets from older relay transport versions.
253
198
 
254
199
  ## Remote Tool Invocation (`tools.invoke`)
255
200
 
256
- The `tools.invoke` gateway method allows the browser to call plugin tools over WebSocket (used in relay mode where there is no HTTP path). This is **not** a generic RPC gateway — it is scoped exclusively to the tools registered by **this plugin**:
201
+ The `tools.invoke` gateway method allows the browser to call plugin tools over WebSocket transport when direct HTTP tool calls are unavailable. This is **not** a generic RPC gateway — it is scoped exclusively to the tools registered by **this plugin**:
257
202
 
258
203
  | Tool | What it can access | Restrictions |
259
204
  |---|---|---|
@@ -291,7 +236,7 @@ Configure in your gateway's `openclaw.json` under the plugin section:
291
236
 
292
237
  | Key | Type | Default | Description |
293
238
  |---|---|---|---|
294
- | `fs.allowedRoots` | `string[]` | `[resolved OpenClaw state dir]` | Restrict filesystem operations to these directories. Hardcoded blocks on `credentials/`, `devices/`, `identity/`, `relay/squad-relay.json`, and `.bak` files always apply regardless. |
239
+ | `fs.allowedRoots` | `string[]` | `[resolved OpenClaw state dir]` | Restrict filesystem operations to these directories. Hardcoded blocks on `credentials/`, `devices/`, `identity/`, legacy `relay/squad-relay.json`, and `.bak` files always apply regardless. |
295
240
 
296
241
  ## Source Code
297
242
 
@@ -300,9 +245,9 @@ Configure in your gateway's `openclaw.json` under the plugin section:
300
245
  - **Security-critical files:**
301
246
  - `src/filesystem.ts` — path blocking, redaction, write protection
302
247
  - `src/sql.ts` — restricted SQL execution
303
- - `src/relay-client.ts` — relay authentication, E2E encryption, device identity, operator token handling
304
- - `src/device-keys.ts` — ed25519 key generation, device identity management
305
- - `src/e2e-crypto.ts` — ECDH key exchange, AES-256-GCM encryption
248
+ - `src/http-routes.ts` — Tailnet internal routes and browser-proof validation
249
+ - `src/shared-api.ts` — gateway method + tool registration boundaries
250
+ - `src/relay-client.ts`, `src/device-keys.ts`, `src/e2e-crypto.ts` — legacy relay transport components retained in repository but not in the active Tailnet-direct setup path
306
251
 
307
252
  ## License
308
253