squad-openclaw 2026.2.2209 → 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.
- package/README.md +56 -111
- package/dist/index.js +1155 -1853
- 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
|
|
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
|
-
| `
|
|
14
|
-
| `
|
|
15
|
-
|
|
|
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` |
|
|
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
|
|
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
|
-
##
|
|
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
|
|
131
|
+
The plugin persists runtime safety state in:
|
|
152
132
|
|
|
153
|
-
|
|
133
|
+
- `~/.openclaw/squad-ceo-data/safety/plugin-state.json`
|
|
154
134
|
|
|
155
|
-
|
|
156
|
-
- `SQUAD_RELAY_URL` (fallback)
|
|
135
|
+
State values:
|
|
157
136
|
|
|
158
|
-
|
|
137
|
+
- `ACTIVE`
|
|
138
|
+
- `DISABLED_MANUAL`
|
|
139
|
+
- `QUARANTINED_AUTO`
|
|
159
140
|
|
|
160
|
-
|
|
141
|
+
Behavior:
|
|
161
142
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
155
|
+
Recovery paths:
|
|
204
156
|
|
|
205
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
169
|
+
### Pairing Model (Current)
|
|
221
170
|
|
|
222
|
-
|
|
171
|
+
Pairing does **not** rely on plugin-owned approval state:
|
|
223
172
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
180
|
+
This means approval is native to gateway pairing and not stored in plugin-local state.
|
|
229
181
|
|
|
230
|
-
|
|
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
|
-
|
|
184
|
+
Pairing helper routes enforce:
|
|
237
185
|
|
|
238
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
195
|
+
### Legacy Relay-State File Handling
|
|
247
196
|
|
|
248
|
-
|
|
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
|
|
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/
|
|
304
|
-
- `src/
|
|
305
|
-
- `src/e2e-crypto.ts` —
|
|
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
|
|