squad-openclaw 2026.2.16 → 2026.2.27

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 ADDED
@@ -0,0 +1,309 @@
1
+ # squad-openclaw
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.
4
+
5
+ ## Features
6
+
7
+ | Tool / Method | Description |
8
+ |---|---|
9
+ | `entity_list`, `entity_search`, `entity_sync` | In-memory entity registry with filesystem watching (agents, skills, plugins, tools, media) |
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) |
16
+
17
+ ## State Directory Resolution
18
+
19
+ All paths in this plugin (and throughout this README) that reference `~/.openclaw` resolve via environment override when set. This supports Docker and other containerized deployments where the OpenClaw data directory may not be at the default location.
20
+
21
+ Resolution priority:
22
+
23
+ 1. `OPENCLAW_STATE_DIR` (canonical)
24
+ 2. `OPENCLAW_DIR` (compat alias)
25
+ 3. `os.homedir() + "/.openclaw"` (default)
26
+
27
+ | Environment | Typical path | How it's resolved |
28
+ |---|---|---|
29
+ | Standard install | `~/.openclaw` | Default — `os.homedir() + "/.openclaw"` |
30
+ | Docker | `/data/.openclaw` | `OPENCLAW_STATE_DIR=/data/.openclaw` (or `OPENCLAW_DIR=/data/.openclaw`) |
31
+ | Custom / NAS | `/mnt/data/.openclaw` | `OPENCLAW_STATE_DIR=/mnt/data/.openclaw` |
32
+
33
+ **This variable only controls where the plugin looks for OpenClaw's own data directory.** It does not grant the plugin access to the parent directory or any other part of the filesystem. All security restrictions (blocked directories, allowed roots, write protection) are enforced relative to the resolved state directory — not the filesystem root.
34
+
35
+ The resolution logic lives in a single shared module ([`src/paths.ts`](src/paths.ts)) imported by every file that needs the state directory path.
36
+
37
+ ### Docker Workspace Mapping
38
+
39
+ If your container mounts the real workspace outside the OpenClaw state directory (for example, `/data/workspace`), create a symlink so OpenClaw-compatible tools can still resolve `.../.openclaw/workspace`:
40
+
41
+ ```bash
42
+ mkdir -p /data/.openclaw /data/workspace
43
+ ln -sfn /data/workspace /data/.openclaw/workspace
44
+ ```
45
+
46
+ Then start your gateway/server with explicit env vars (useful when `.env` is not loaded):
47
+
48
+ ```bash
49
+ PORT=3334 \
50
+ OPENCLAW_STATE_DIR=/data/.openclaw \
51
+ OPENCLAW_DIR=/data/.openclaw \
52
+ node server.js
53
+ ```
54
+
55
+ Quick verification:
56
+
57
+ ```bash
58
+ ls -ld /data/.openclaw /data/.openclaw/workspace /data/workspace /data/.openclaw/openclaw.json
59
+ ```
60
+
61
+ ## Security Model
62
+
63
+ This plugin enforces a **defense-in-depth** security model with four independent layers. All security rules are hard-coded and non-configurable (except `allowedRoots`) so they can be verified by reading the source code. The bundle is intentionally **not minified** to allow security auditing of the distributed code.
64
+
65
+ > **Note:** Throughout this section, `~/.openclaw` refers to the resolved state directory (see above). In Docker or custom installs, substitute the actual path set by `OPENCLAW_STATE_DIR`.
66
+
67
+ ### Layer 1: Blocked Directories (hardcoded, non-configurable)
68
+
69
+ These directories are **completely blocked** from all filesystem operations (read, write, list, delete, rename):
70
+
71
+ | Path | Contents |
72
+ |---|---|
73
+ | `~/.openclaw/credentials/` | OAuth tokens, API keys |
74
+ | `~/.openclaw/devices/` | Device pairing secrets, private keys |
75
+ | `~/.openclaw/identity/` | Operator identity material |
76
+
77
+ ### Layer 1b: Blocked Files (hardcoded, non-configurable)
78
+
79
+ | Path | Reason |
80
+ |---|---|
81
+ | `~/.openclaw/squad-ceo-data/relay/squad-relay.json` | Contains ed25519 private key for relay device identity |
82
+ | `~/.openclaw/*.bak` | Backup files at the top level contain unredacted config (tokens, keys) that would bypass redaction |
83
+
84
+ ### Layer 2: Redacted Files (hardcoded, non-configurable)
85
+
86
+ `~/.openclaw/openclaw.json` is **readable** but with sensitive fields replaced with `"[REDACTED]"` before returning to clients:
87
+
88
+ - `channels.*.botToken` — channel bot tokens
89
+ - `gateway.auth.*` — all auth keys
90
+ - `gateway.token` — legacy token location
91
+ - `gateway.remote.token` — legacy remote token
92
+
93
+ ### Layer 3: Allowed Roots (configurable, defaults to `~/.openclaw`)
94
+
95
+ Filesystem operations are restricted to configured root directories. By default, only `~/.openclaw/` is accessible — covering all application needs:
96
+
97
+ - `~/.openclaw/squad-ceo-data/` — entity databases, data files
98
+ - `~/.openclaw/media/` — asset uploads
99
+ - `~/.openclaw/workspace*/` — agent workspaces
100
+ - `~/.openclaw/skills/` — skill definitions
101
+ - `~/.openclaw/extensions/` — plugin manifests
102
+
103
+ Operators can customize via the `fs.allowedRoots` config option.
104
+
105
+ ### Layer 4: Filesystem Write Protection + Surgical Gateway Mutations
106
+
107
+ These files/directories cannot be written via filesystem tools (`fs_write`, `fs_rename`, etc.), even if they fall within `allowedRoots`:
108
+
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
111
+ - All blocked directories above (credentials, devices, identity)
112
+ - All `.bak` files at `~/.openclaw/` top level
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
+ ### Startup Config Migration (one-time, localized)
122
+
123
+ On plugin startup, Squad runs an internal migration harness. Current migration behavior:
124
+
125
+ - Checks `~/.openclaw/squad-ceo-data/migrations.json` for completed migration IDs.
126
+ - If `001-enable-main-subagent-access` is not recorded, applies a **localized** `config.patch` that sets only:
127
+ - `agents.defaults.maxConcurrent = 4`
128
+ - `agents.defaults.subagents.maxConcurrent = 8`
129
+ - `agents.list[id=main].identity.name = "Pepper"`
130
+ - `agents.list[id=main].tools.allow = ["*"]`
131
+ - `agents.list[id=main].subagents.allowAgents = ["*"]`
132
+ - Records completion in `~/.openclaw/squad-ceo-data/migrations.json`.
133
+
134
+ 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
+
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.
152
+
153
+ For local testing, the relay endpoint can be overridden with environment variables:
154
+
155
+ - `OPENCLAW_SQUAD_RELAY_URL` (preferred)
156
+ - `SQUAD_RELAY_URL` (fallback)
157
+
158
+ If neither is set, the plugin uses the production default `wss://relay.squad.ceo`.
159
+
160
+ ### How the Relay Connection Works
161
+
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:
185
+
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:
200
+
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.
202
+
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.
204
+
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.
206
+
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.
208
+
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
+ ```
217
+
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.
219
+
220
+ ### Device Identity and Auto-Pairing
221
+
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:
223
+
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)
227
+
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:
229
+
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
235
+
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.
237
+
238
+ ### E2E Encryption
239
+
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.
243
+
244
+ ### Authentication Chain Summary
245
+
246
+ For a browser user's RPC call to reach the gateway through the relay, **all of the following must be valid:**
247
+
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)
253
+
254
+ ## Remote Tool Invocation (`tools.invoke`)
255
+
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**:
257
+
258
+ | Tool | What it can access | Restrictions |
259
+ |---|---|---|
260
+ | `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
+ | `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
+
265
+ 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
+
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
+ ## Build Transparency
279
+
280
+ The build configuration (`tsup.config.ts`) is optimized for security auditing:
281
+
282
+ | Setting | Value | Reason |
283
+ |---|---|---|
284
+ | `minify` | `false` | Unminified bundle for human/AI security review |
285
+ | `sourcemap` | `false` | No internal path exposure |
286
+ | `treeshake` | `false` | All code preserved for complete auditing |
287
+
288
+ ## Configuration
289
+
290
+ Configure in your gateway's `openclaw.json` under the plugin section:
291
+
292
+ | Key | Type | Default | Description |
293
+ |---|---|---|---|
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. |
295
+
296
+ ## Source Code
297
+
298
+ - **Repository:** [github.com/WorldBrain/squad](https://github.com/WorldBrain/squad)
299
+ - **Plugin directory:** `extensions/squad-openclaw/`
300
+ - **Security-critical files:**
301
+ - `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
306
+
307
+ ## License
308
+
309
+ MIT
package/dist/index.d.ts CHANGED
@@ -1,11 +1,3 @@
1
- /**
2
- * squad-openclaw — OpenClaw gateway plugin for Squad
3
- *
4
- * Provides:
5
- * - Entity registry with FTS and vector search (entity_*)
6
- * - Filesystem tools for remote clients (fs_read, fs_write, fs_list)
7
- * - Version check and self-update gateway methods (squad.version.*)
8
- */
9
1
  declare function squadAppPlugin(api: any): void;
10
2
 
11
3
  export { squadAppPlugin as default };