docs/gateway/pairing.md
In Gateway-owned pairing, the Gateway is the source of truth for which nodes may 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, legacy pairing store and does not gate the WS
handshake. Only clients that explicitly call node.pair.* use this flow.
node.pair.requested.Pending requests expire automatically 5 minutes after the node's last retry — an actively reconnecting node keeps its one pending request alive rather than generating a fresh request (and approval prompt) per attempt.
openclaw nodes pending
openclaw nodes approve <requestId>
openclaw nodes reject <requestId>
openclaw nodes status
openclaw nodes remove --node <id|name|ip>
openclaw nodes rename --node <id|name|ip> --name "Living Room iPad"
nodes status shows paired/connected nodes and their capabilities.
Events:
node.pair.requested - emitted when a new pending request is created.node.pair.resolved - emitted when a request is approved, rejected, or
expired.Methods:
node.pair.request - create or reuse a pending request.node.pair.list - list pending and paired nodes (operator.pairing).node.pair.approve - approve a pending request (issues a token).node.pair.reject - reject a pending request.node.pair.remove - remove a paired node. For a device-backed pairing, this
revokes the device's node role: it mutates devices/paired.json and
invalidates/disconnects that device's node-role sessions. A mixed-role
device (for example one that also holds operator) keeps its row and only
loses the node role; a node-only device row is deleted. It also clears any
matching legacy gateway-owned node pairing entry. Authz: operator.pairing
may remove non-operator node rows; a device-token caller revoking its
own node role on a mixed-role device additionally needs
operator.admin.node.pair.verify - verify { nodeId, token }.Notes:
node.pair.request is idempotent per node: repeated calls return the same
pending request.node.pair.request never
returns a token.silent: true as a hint for auto-approval flows.node.pair.approve uses the pending request's declared commands to enforce
extra approval scopes:
operator.pairingoperator.pairing + operator.writesystem.run / system.run.prepare / system.which request:
operator.pairing + operator.admingateway.nodes.allowCommands and
denyCommands).system.run allow and ask policy lives on the node in
exec.approvals.node.*, not in the pairing record.When a node connects for the first time, pairing is requested automatically. Until that request is approved, all pending node commands from that node are filtered and will not execute. Once pairing is approved, the node's declared commands become available, subject to the normal command policy.
This means:
Node-originated summaries and related session events are restricted to the intended trusted surface. Notification-driven or node-triggered flows that previously relied on broader host or session tool access may need adjustment. This hardening keeps node events from escalating into host-level tool access beyond what the node's trust boundary permits.
Durable node presence updates follow the same identity boundary: the
node.presence.alive event is accepted only from authenticated node device
sessions, and updates pairing metadata only when the device/node identity is
already paired. A self-declared client.id value is not enough to write
last-seen state.
The macOS app can attempt a silent approval when:
silent, andIf silent approval fails, it falls back to the normal Approve/Reject prompt.
WS device pairing for role: node stays manual by default. For private node
networks where the Gateway already trusts the network path, operators can opt
in with explicit CIDRs or exact IPs:
{
gateway: {
nodes: {
pairing: {
autoApproveCidrs: ["192.168.1.0/24"],
},
},
},
}
Security boundary:
gateway.nodes.pairing.autoApproveCidrs is unset.role: node device pairing request with no requested scopes is
eligible.When an already-paired device reconnects with only non-sensitive metadata
changes (for example display name or client platform hints), OpenClaw treats
that as a metadata-upgrade. Silent auto-approval is narrow: it applies only
to trusted non-browser local reconnects that already proved possession of
local or shared credentials, including same-host native app reconnects after
OS version metadata changes. Browser/Control UI clients and remote clients
still use the explicit re-approval flow. Scope upgrades (read to
write/admin) and public key changes are not eligible for
metadata-upgrade auto-approval; they stay explicit re-approval requests.
/pair qr renders the pairing payload as structured media so mobile and
browser clients can scan it directly.
Deleting a device also sweeps any stale pending pairing requests for that
device id, so nodes pending does not show orphaned rows after a revoke.
Gateway pairing treats a connection as loopback only when both the raw socket
and any upstream proxy evidence agree. If a request arrives on loopback but
carries Forwarded, any X-Forwarded-*, or X-Real-IP header evidence, that
forwarded-header evidence disqualifies the loopback locality claim, and the
pairing path requires explicit approval instead of silently treating the
request as a same-host connect. See
Trusted Proxy Auth for the equivalent rule on
operator auth.
Pairing state is stored under the Gateway state directory (default
~/.openclaw):
~/.openclaw/nodes/paired.json~/.openclaw/nodes/pending.jsonIf you override OPENCLAW_STATE_DIR, the nodes/ folder moves with it.
Security notes:
paired.json as sensitive.