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.
- package/README.md +87 -125
- package/dist/index.js +1123 -1536
- 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,
|
|
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
|
-
| `
|
|
12
|
-
| `squad.version.check
|
|
13
|
-
| `squad.
|
|
14
|
-
| `tools.invoke` |
|
|
15
|
-
|
|
|
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` |
|
|
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
|
|
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
|
|
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
|
-
##
|
|
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
|
-
|
|
148
|
+
The plugin persists runtime safety state in:
|
|
154
149
|
|
|
155
|
-
- `
|
|
156
|
-
- `SQUAD_RELAY_URL` (fallback)
|
|
150
|
+
- `~/.openclaw/squad-ceo-data/safety/plugin-state.json`
|
|
157
151
|
|
|
158
|
-
|
|
152
|
+
State values:
|
|
159
153
|
|
|
160
|
-
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
164
|
+
Environment controls:
|
|
183
165
|
|
|
184
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
178
|
+
## Tailnet Pairing and Locator Security
|
|
200
179
|
|
|
201
|
-
|
|
180
|
+
The active onboarding path is Tailnet-direct and gateway-owned:
|
|
202
181
|
|
|
203
|
-
|
|
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
|
-
|
|
186
|
+
### Pairing Model (Current)
|
|
206
187
|
|
|
207
|
-
|
|
188
|
+
Pairing does **not** rely on plugin-owned approval state:
|
|
208
189
|
|
|
209
|
-
|
|
210
|
-
Browser
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
197
|
+
This means approval is native to gateway pairing and not stored in plugin-local state.
|
|
219
198
|
|
|
220
|
-
###
|
|
199
|
+
### Plugin Route Security (`/squad-internal/pairing/*`)
|
|
221
200
|
|
|
222
|
-
|
|
201
|
+
Pairing helper routes enforce:
|
|
223
202
|
|
|
224
|
-
-
|
|
225
|
-
-
|
|
226
|
-
-
|
|
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
|
-
|
|
208
|
+
### Pairing Helper Routes (Compatibility)
|
|
229
209
|
|
|
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
|
|
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
|
-
|
|
212
|
+
`POST /squad-internal/pairing/request` expects browser proof fields:
|
|
237
213
|
|
|
238
|
-
|
|
214
|
+
- `deviceId`
|
|
215
|
+
- `publicKey` (Ed25519) **or** `publicKeyJwk` (P-256)
|
|
216
|
+
- `signature`
|
|
217
|
+
- `nonce`
|
|
218
|
+
- `signedAt`
|
|
239
219
|
|
|
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.
|
|
220
|
+
`GET /squad-internal/pairing/status` expects:
|
|
243
221
|
|
|
244
|
-
|
|
222
|
+
- `requestId` query param
|
|
223
|
+
- `deviceId` query param
|
|
245
224
|
|
|
246
|
-
|
|
225
|
+
### Legacy Relay-State File Handling
|
|
247
226
|
|
|
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)
|
|
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
|
|
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/
|
|
303
|
-
- `src/
|
|
304
|
-
- `src/
|
|
305
|
-
- `src/
|
|
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
|
|