squad-openclaw 2026.2.2702 → 2026.2.2704

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 +87 -125
  2. package/dist/index.js +1123 -1536
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,18 +1,35 @@
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, locked-down filesystem tools, agent setup wrappers, plugin safety controls, and Tailnet internal routes for onboarding helpers.
4
4
 
5
5
  ## Features
6
6
 
7
- | Tool / Method | Description |
7
+ | Tool / Method / Endpoint | Description |
8
8
  |---|---|
9
9
  | `entity_list`, `entity_search`, `entity_sync` | In-memory entity registry with filesystem watching (agents, skills, plugins, tools, media) |
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
- | `sql_query` | Restricted SQLite query tool `sqlite3` only, scoped to `~/.openclaw/squad-ceo-data/` |
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) |
11
+ | `squad.agents.add` | Plugin wrapper for creating agent scaffolding (workspace seed files, sessions skeleton, and config list entry) |
12
+ | `squad.version.check` | Plugin version reporting |
13
+ | `squad.questions.validate-envelope` | HUMAN_INPUT_REQUIRED envelope validation |
14
+ | `tools.invoke`, `tools.list`, `squad.layout.get` | Core plugin RPC entrypoints for tool invocation/listing and gateway layout metadata |
15
+ | `squad.plugin.status`, `squad.plugin.recover`, `squad.plugin.disable` | Plugin safety-state RPC control (status, recovery, manual disable) |
16
+ | `GET /squad-internal/health` | Tailnet internal health + pairing capability metadata |
17
+ | `GET /squad-internal/plugin/status` | Tailnet internal plugin safety-state status |
18
+ | `POST /squad-internal/plugin/recover`, `POST /squad-internal/plugin/disable` | Tailnet internal plugin safety-state controls |
19
+ | `POST /squad-internal/pairing/request`, `GET /squad-internal/pairing/status` | Tailnet internal pairing helper routes with origin/Tailnet/browser-proof checks |
20
+
21
+ ## Internal Endpoint Reference
22
+
23
+ All `/squad-internal/*` routes enforce Tailnet context and origin checks. CORS preflight (`OPTIONS`) is handled for these routes.
24
+
25
+ | Route | Method | Purpose |
26
+ |---|---|---|
27
+ | `/squad-internal/health` | `GET` | Returns plugin safety snapshot and pairing capability hints |
28
+ | `/squad-internal/plugin/status` | `GET` | Returns current plugin safety state |
29
+ | `/squad-internal/plugin/recover` | `POST` | Recovers plugin safety state back to active (unless env kill-switch is set) |
30
+ | `/squad-internal/plugin/disable` | `POST` | Manually disables plugin safety state |
31
+ | `/squad-internal/pairing/request` | `POST` | Creates pairing request via gateway-native pairing methods (`node/devices/device.pair.request`) |
32
+ | `/squad-internal/pairing/status` | `GET` | Resolves pairing status via gateway-native status methods (`node/devices/device.pair.status/get`) |
16
33
 
17
34
  ## State Directory Resolution
18
35
 
@@ -78,7 +95,7 @@ These directories are **completely blocked** from all filesystem operations (rea
78
95
 
79
96
  | Path | Reason |
80
97
  |---|---|
81
- | `~/.openclaw/squad-ceo-data/relay/squad-relay.json` | Contains ed25519 private key for relay device identity |
98
+ | `~/.openclaw/squad-ceo-data/relay/squad-relay.json` | Legacy relay-state material from older transport flows (blocked to prevent accidental secret disclosure) |
82
99
  | `~/.openclaw/*.bak` | Backup files at the top level contain unredacted config (tokens, keys) that would bypass redaction |
83
100
 
84
101
  ### Layer 2: Redacted Files (hardcoded, non-configurable)
@@ -102,22 +119,15 @@ Filesystem operations are restricted to configured root directories. By default,
102
119
 
103
120
  Operators can customize via the `fs.allowedRoots` config option.
104
121
 
105
- ### Layer 4: Filesystem Write Protection + Surgical Gateway Mutations
122
+ ### Layer 4: Filesystem Write Protection
106
123
 
107
124
  These files/directories cannot be written via filesystem tools (`fs_write`, `fs_rename`, etc.), even if they fall within `allowedRoots`:
108
125
 
109
126
  - `~/.openclaw/openclaw.json` — operator configuration (tool-level read-only with redaction)
110
- - `~/.openclaw/squad-ceo-data/relay/squad-relay.json` — relay device private key
127
+ - `~/.openclaw/squad-ceo-data/relay/squad-relay.json` — legacy relay-state secret file (blocked)
111
128
  - All blocked directories above (credentials, devices, identity)
112
129
  - All `.bak` files at `~/.openclaw/` top level
113
130
 
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
131
  ### Startup Config Migration (one-time, localized)
122
132
 
123
133
  On plugin startup, Squad runs an internal migration harness. Current migration behavior:
@@ -133,148 +143,100 @@ On plugin startup, Squad runs an internal migration harness. Current migration b
133
143
 
134
144
  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
145
 
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()`.
150
-
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.
146
+ ## Plugin Safety State (Kill Switch + Quarantine)
152
147
 
153
- For local testing, the relay endpoint can be overridden with environment variables:
148
+ The plugin persists runtime safety state in:
154
149
 
155
- - `OPENCLAW_SQUAD_RELAY_URL` (preferred)
156
- - `SQUAD_RELAY_URL` (fallback)
150
+ - `~/.openclaw/squad-ceo-data/safety/plugin-state.json`
157
151
 
158
- If neither is set, the plugin uses the production default `wss://relay.squad.ceo`.
152
+ State values:
159
153
 
160
- ### How the Relay Connection Works
154
+ - `ACTIVE`
155
+ - `DISABLED_MANUAL`
156
+ - `QUARANTINED_AUTO`
161
157
 
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
- ```
158
+ Behavior:
177
159
 
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.
160
+ - If disabled/quarantined, plugin methods return typed errors (`PLUGIN_DISABLED` / `PLUGIN_QUARANTINED`) instead of crashing startup/runtime.
161
+ - Quarantine auto-expires after a cooldown window and transitions back to `ACTIVE`.
162
+ - Environment kill switch overrides persisted state and is authoritative.
181
163
 
182
- ### What Data Crosses the Network (relay.squad.ceo)
164
+ Environment controls:
183
165
 
184
- The relay server is a message router. Here is exactly what it sees and does not see:
166
+ - `SQUAD_PLUGIN_DISABLED=1` force disable plugin
167
+ - `SQUAD_PLUGIN_DISABLE_REASON="..."` — optional operator-facing reason
168
+ - `SQUAD_PLUGIN_FAILURE_THRESHOLD` — failures before auto-quarantine (default `3`)
169
+ - `SQUAD_PLUGIN_FAILURE_WINDOW_MS` — failure counting window (default `300000`)
170
+ - `SQUAD_PLUGIN_QUARANTINE_MS` — quarantine duration (default `600000`)
185
171
 
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 |
172
+ Recovery paths:
196
173
 
197
- ### Operator Token Never Leaves the Server
174
+ - RPC: `openclaw gateway call squad.plugin.recover --json`
175
+ - HTTP (Tailnet): `POST /squad-internal/plugin/recover`
176
+ - If env kill switch is active, unset `SQUAD_PLUGIN_DISABLED` and restart gateway.
198
177
 
199
- The operator token (`gateway.auth.token` in `~/.openclaw/openclaw.json`) is the most sensitive credential. Here is exactly how it flows:
178
+ ## Tailnet Pairing and Locator Security
200
179
 
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.
180
+ The active onboarding path is Tailnet-direct and gateway-owned:
202
181
 
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.
182
+ - Browser connects directly to `wss://<gateway>.ts.net`
183
+ - OpenClaw gateway enforces pairing approval state
184
+ - Relay service is used for account auth and locator metadata only
204
185
 
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.
186
+ ### Pairing Model (Current)
206
187
 
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.
188
+ Pairing does **not** rely on plugin-owned approval state:
208
189
 
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
- ```
190
+ 1. Browser receives gateway `connect.challenge`
191
+ 2. Browser signs challenge with its per-browser device key
192
+ 3. Browser sends native `connect` request
193
+ 4. If unpaired, gateway returns pairing-required error (with `requestId` when available)
194
+ 5. UI shows `openclaw devices approve <requestId>`
195
+ 6. UI polls reconnect automatically in the background until approved
217
196
 
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.
197
+ This means approval is native to gateway pairing and not stored in plugin-local state.
219
198
 
220
- ### Device Identity and Auto-Pairing
199
+ ### Plugin Route Security (`/squad-internal/pairing/*`)
221
200
 
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:
201
+ Pairing helper routes enforce:
223
202
 
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)
203
+ - Allowed browser origin (`SQUAD_ALLOWED_ORIGINS` or built-in defaults)
204
+ - Tailnet context (Tailnet headers/hostname checks)
205
+ - Browser proof (device ID/public key/signature/nonce/signedAt) on pairing request creation
206
+ - Nonce freshness and per-device/IP rate limits
227
207
 
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:
208
+ ### Pairing Helper Routes (Compatibility)
229
209
 
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
210
+ `/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.
235
211
 
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.
212
+ `POST /squad-internal/pairing/request` expects browser proof fields:
237
213
 
238
- ### E2E Encryption
214
+ - `deviceId`
215
+ - `publicKey` (Ed25519) **or** `publicKeyJwk` (P-256)
216
+ - `signature`
217
+ - `nonce`
218
+ - `signedAt`
239
219
 
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.
220
+ `GET /squad-internal/pairing/status` expects:
243
221
 
244
- ### Authentication Chain Summary
222
+ - `requestId` query param
223
+ - `deviceId` query param
245
224
 
246
- For a browser user's RPC call to reach the gateway through the relay, **all of the following must be valid:**
225
+ ### Legacy Relay-State File Handling
247
226
 
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)
227
+ `~/.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
228
 
254
229
  ## Remote Tool Invocation (`tools.invoke`)
255
230
 
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**:
231
+ 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
232
 
258
233
  | Tool | What it can access | Restrictions |
259
234
  |---|---|---|
260
235
  | `fs_read`, `fs_write`, `fs_list`, `fs_delete`, `fs_rename`, `fs_mkdir` | `~/.openclaw/` only | All 4 security layers apply (blocked dirs, blocked files, redaction, allowed roots, write protection) |
261
- | `sql_query` | `~/.openclaw/squad-ceo-data/*.db` only | sqlite3 only, no shell, no command injection (see below) |
262
236
  | `entity_list`, `entity_search`, `entity_sync` | In-memory entity index | Read-only metadata (names, types, paths) |
263
- | `squad.version.check`, `squad.version.update` | npm registry | Read-only check + controlled `npm install` |
264
237
 
265
238
  It **cannot** invoke gateway core tools (`exec`, `bash`, `read`, `write`, `web_fetch`, etc.) — only the tools this plugin registers via `api.registerTool()`. Every invoked tool enforces its own security restrictions independently — `tools.invoke` is just a transport layer, not a privilege escalation.
266
239
 
267
- ## SQL Query Tool
268
-
269
- > **`sql_query` can only access the plugin's own application data** in `~/.openclaw/squad-ceo-data/`. It cannot read or modify any other files on the system — not system databases, not user documents, not gateway configuration.
270
-
271
- The `sql_query` tool provides restricted SQLite access:
272
-
273
- - **Path restriction:** Database files must be within `~/.openclaw/squad-ceo-data/` — the plugin's own data directory containing entity registries and application state. Paths outside this directory are rejected before any query is executed.
274
- - **No shell:** Uses `execFile` (not `exec`) — arguments are passed as an argv array, preventing command injection
275
- - **No arbitrary commands:** Only `sqlite3` is executed — no other binary can be invoked through this tool
276
- - **Data scope:** The databases in `squad-ceo-data/` contain only Squad application data (entity metadata, search indexes, user preferences). No credentials, tokens, or gateway configuration is stored in these databases.
277
-
278
240
  ## Build Transparency
279
241
 
280
242
  The build configuration (`tsup.config.ts`) is optimized for security auditing:
@@ -291,7 +253,7 @@ Configure in your gateway's `openclaw.json` under the plugin section:
291
253
 
292
254
  | Key | Type | Default | Description |
293
255
  |---|---|---|---|
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. |
256
+ | `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
257
 
296
258
  ## Source Code
297
259
 
@@ -299,10 +261,10 @@ Configure in your gateway's `openclaw.json` under the plugin section:
299
261
  - **Plugin directory:** `extensions/squad-openclaw/`
300
262
  - **Security-critical files:**
301
263
  - `src/filesystem.ts` — path blocking, redaction, write protection
302
- - `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
264
+ - `src/agents.ts` — wrapper for agent setup (workspace/session skeleton + config entry update)
265
+ - `src/http-routes.ts` — Tailnet internal routes and browser-proof validation
266
+ - `src/shared-api.ts` — gateway method + tool registration boundaries
267
+ - `src/plugin-safety-gateway.ts`, `src/plugin-safety-state.ts`plugin safety-state control and persistence
306
268
 
307
269
  ## License
308
270