--- summary: "Gateway-owned node pairing (Option B) for iOS and other remote nodes" read_when: - Implementing node pairing approvals without macOS UI - Adding CLI flows for approving remote nodes - Extending gateway protocol with node management --- # Gateway-owned pairing (Option B) In Gateway-owned pairing, the **Gateway** is the source of truth for which nodes are allowed to join. UIs (macOS app, future clients) are just frontends that approve or reject pending requests. **Important:** WS nodes use **device pairing** (role `node`) during `connect`. `node.pair.*` is a separate pairing store and does **not** gate the WS handshake. Only clients that explicitly call `node.pair.*` use this flow. ## Concepts - **Pending request**: a node asked to join; requires approval. - **Paired node**: approved node with an issued auth token. - **Transport**: the Gateway WS endpoint forwards requests but does not decide membership. (Legacy TCP bridge support is deprecated/removed.) ## How pairing works 1. A node connects to the Gateway WS and requests pairing. 2. The Gateway stores a **pending request** and emits `node.pair.requested`. 3. You approve or reject the request (CLI or UI). 4. On approval, the Gateway issues a **new token** (tokens are rotated on re‑pair). 5. The node reconnects using the token and is now “paired”. Pending requests expire automatically after **5 minutes**. ## CLI workflow (headless friendly) ```bash openclaw nodes pending openclaw nodes approve openclaw nodes reject openclaw nodes status openclaw nodes rename --node --name "Living Room iPad" ``` `nodes status` shows paired/connected nodes and their capabilities. ## API surface (gateway protocol) Events: - `node.pair.requested` — emitted when a new pending request is created. - `node.pair.resolved` — emitted when a request is approved/rejected/expired. Methods: - `node.pair.request` — create or reuse a pending request. - `node.pair.list` — list pending + paired nodes. - `node.pair.approve` — approve a pending request (issues token). - `node.pair.reject` — reject a pending request. - `node.pair.verify` — verify `{ nodeId, token }`. Notes: - `node.pair.request` is idempotent per node: repeated calls return the same pending request. - Approval **always** generates a fresh token; no token is ever returned from `node.pair.request`. - Requests may include `silent: true` as a hint for auto-approval flows. ## Auto-approval Auto-approval allows trusted nodes to connect without manual intervention. There are two mechanisms: ### macOS app (SSH verification) The macOS app can optionally attempt a **silent approval** when: - the request is marked `silent`, and - the app can verify an SSH connection to the gateway host using the same user. If silent approval fails, it falls back to the normal "Approve/Reject" prompt. ### Configuration-based auto-approval For automated deployments (Kubernetes, CI runners, etc.), you can configure the gateway to auto-approve nodes based on role, IP address, and token validation. ```json5 { gateway: { nodes: { autoApprove: { enabled: true, roles: ["node"], // Only auto-approve "node" role ipAllowlist: ["10.0.0.0/8"], // Only from private network requireToken: true, // Must have valid gateway token auditLog: true // Log all auto-approvals } } } } ``` **Security layers (all must pass):** 1. `enabled: true` - Feature must be explicitly enabled 2. Role must be in `roles` array 3. Client IP must match `ipAllowlist` (or be localhost if empty) 4. Valid gateway token required (when `requireToken: true`) Localhost connections (127.0.0.1, ::1) are always auto-approved for backward compatibility. See [gateway.nodes.autoApprove](/gateway/configuration#gatewaynodes-node-management) for the full configuration reference. ## Storage (local, private) Pairing state is stored under the Gateway state directory (default `~/.openclaw`): - `~/.openclaw/nodes/paired.json` - `~/.openclaw/nodes/pending.json` If you override `OPENCLAW_STATE_DIR`, the `nodes/` folder moves with it. Security notes: - Tokens are secrets; treat `paired.json` as sensitive. - Rotating a token requires re-approval (or deleting the node entry). ## Transport behavior - The transport is **stateless**; it does not store membership. - If the Gateway is offline or pairing is disabled, nodes cannot pair. - If the Gateway is in remote mode, pairing still happens against the remote Gateway’s store.