Node Pairing and Capability Approval in the Gateway

Learn how node pairing and capability approval work in the Gateway, including device pairing and node.pair.* approvals. Essential for developers integrating nodes and managing command exposure.

Read this when

  • Implementing node pairing approvals without macOS UI
  • Adding CLI flows for approving remote nodes
  • Extending gateway protocol with node management

Node pairing operates on two distinct levels, and both are recorded on the paired device entry in the Gateway's SQLite state database:

  • Device pairing (role node) controls access to the connect handshake. Refer to Trusted-CIDR device auto-approval further down and Channel pairing.
  • Node capability approval (node.pair.*) determines which declared capabilities or commands a connected node is allowed to present. The Gateway holds the authoritative state; UIs such as the macOS app and Control UI act as frontends for approving or rejecting pending requests.

The previous standalone node pairing store (nodes/paired.json with a per-node token, removed from the connect path in January 2026) no longer exists: at startup, gateways merge any leftover rows into the device records once and archive the legacy files with a .migrated suffix. Support for the legacy TCP bridge has also been dropped.

How capability approval works

  1. A node establishes a connection to the Gateway WS, and device pairing governs this step.
  2. The Gateway compares the declared capability and command surface against the approved one; any new or expanded surface triggers a pending request stored on the device record and fires node.pair.requested.
  3. You can approve or reject the request through the CLI or a UI.
  4. Until approval is granted, node commands remain filtered; once approved, the declared surface becomes available, still subject to the standard command policy.

Pending requests expire automatically 5 minutes after the node's last retry, meaning an actively reconnecting node retains its single pending request instead of spawning a new request and approval prompt with each attempt.

One-paste node pairing

On the Control UI Devices page, open the pairing dialog, select Node host, and copy the generated command onto the device:

openclaw node run --pair "oc-pair://<setup-code>"

The setup link includes the Gateway endpoint, a short-lived single-use bootstrap token, and a TLS certificate pin when the Gateway directly serves a pinnable leaf certificate. The bootstrap token is valid for 10 minutes. Explicit --host, --port, --context-path, --tls/--no-tls, and --tls-fingerprint flags take precedence over values from --pair.

The bootstrap token and the resulting device credential are distinct, much like a short-lived Tailscale auth key versus the durable device identity it grants. Expiring or revoking the setup link does not revoke the paired device; you must remove the device separately if needed. The link never pre-approves system.run or folder sync. Those features still rely on pending approval or SSH-verified device auto-approval.

CLI workflow (headless friendly)

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 displays paired and connected nodes along with their capabilities.

API surface (gateway protocol)

Events:

  • node.pair.requested: fired when a new pending request appears.
  • node.pair.resolved: fired when a request is approved, rejected, or expires.

Methods:

  • node.pair.list: lists pending and paired nodes (operator.pairing).
  • node.pair.approve: approves a pending request.
  • node.pair.reject: rejects a pending request.
  • node.pair.remove: removes a paired node. This action revokes the device's node role in the paired-device store, removes the approved node surface, and invalidates or disconnects that device's node-role sessions. A mixed-role device, such as one that also holds operator, keeps its row and only loses the node role; a node-only device row is deleted entirely. Authz: operator.pairing can remove non-operator node rows; a device-token caller revoking its own node role on a mixed-role device additionally requires operator.admin.
  • node.rename: renames the operator-facing display name of a paired node.

Removed in 2026.7: node.pair.request and node.pair.verify. Pending requests are generated by the Gateway itself during node connects, and the standalone per-node token they relied on is no longer present; node authentication now uses the device pairing token.

Notes:

  • Reconnects with an unchanged surface reuse the pending request; repeated requests refresh the stored node metadata and the latest allowlisted declared command snapshot for operator visibility.
  • Operator scope levels and approval-time checks are covered in Operator scopes.
  • node.pair.approve enforces additional approval scopes using the pending request's declared commands:
    • commandless request: operator.pairing
    • ordinary command request: operator.pairing + operator.write
    • admin-sensitive request containing system.run, system.run.prepare, system.which, browser.proxy, browser.proxy.upload.v1, fs.listDir, or system.execApprovals.get/set: operator.pairing + operator.admin

In this context, fs.listDir is the node command relayed through node.invoke. The top-level Gateway fs.listDir RPC requires operator.write for workspace-contained host browsing and operator.admin when nodeId is present.

Warning

Approving node pairing captures the set of capabilities that are trusted. It does not lock down the live command surface for each individual node.

  • The live command surface is determined by what the node announces upon connection, then filtered through the gateway's global node command policy (gateway.nodes.commands.allow and gateway.nodes.commands.deny).
  • The per-node system.run allow and ask policy is stored on the node itself in exec.approvals.node.*, not within the pairing record.

Node command gating (2026.3.31+)

Warning

Breaking change: as of 2026.3.31, node commands remain inactive until node pairing receives approval. Device pairing by itself no longer suffices to expose declared node commands.

The first time a node connects, a pairing request is generated automatically. Until that request gets approved, all pending node commands originating from that node are filtered out and never execute. After approval, the node's declared commands become accessible, still governed by the standard command policy.

Consequences:

  • Nodes that once depended solely on device pairing to expose commands now need to complete node pairing as well.
  • Commands that were queued prior to pairing approval are discarded, not held for later.

Node event trust boundaries (2026.3.31+)

Warning

Breaking change: runs initiated by nodes now operate on a diminished trusted surface.

Summaries produced by nodes and their associated session events are confined to the intended trusted surface. Notification-driven or node-triggered workflows that previously depended on broader host or session tool access might require changes. This tightening prevents node events from gaining host-level tool access beyond what the node's trust boundary allows.

Durable node presence updates follow the same identity boundary: the node.presence.alive event is only accepted from authenticated node device sessions, and it modifies pairing metadata solely when the device/node identity is already paired. A self-declared client.id value alone cannot write last-seen state.

Silent local pairing

A loopback source address is treated by the Gateway as local. This covers a client reaching a remote loopback-only Gateway via an SSH port forward: the SSH server terminates the connection on the Gateway host, so the Gateway perceives the forwarded connection as loopback. This behavior is deliberate, since ordinary SSH access already implies local trust, including the ability to read the shared Gateway token.

By default, trusted local connections silently approve first-time device pairing as well as role and scope upgrades. This keeps routine same-host and SSH tunnel reconnects smooth. Operators who use shell-less, port-forward-only SSH keys or a multi-user Mac can force explicit approval for every device:

{
  gateway: {
    nodes: {
      pairing: {
        autoApproveLocal: false,
      },
    },
  },
}

With this configuration, new pairing requests, role upgrades, and scope upgrades follow the normal approval flow even for local connections. Metadata-only reconnect refreshes stay automatic, so routine client or OS metadata changes don't generate approval churn.

SSH-verified device auto-approval (default)

First-time role: node device pairing from a private/CGNAT address gets auto-approved when the gateway can prove machine ownership over SSH: it connects back to the pairing host (BatchMode, StrictHostKeyChecking=yes), executes openclaw node identity --json there, and approves only when the remote device id and public key match the pending request exactly. The key match is what provides safety: reachability alone never triggers approval, so NAT co-tenants, other users on a shared host, and LAN spoofing all fall through to the standard prompt.

Enabled by default. Conditions for it to activate:

  • The gateway process user (or sshVerify.user) can SSH to the node host non-interactively (keys/agent; Tailscale SSH also works), and the host key is already trusted.
  • openclaw resolves on the remote PATH for non-interactive sh -lc.
  • The connecting IP is a direct (non-proxied, non-loopback) private, ULA, link-local, or CGNAT address, or matches sshVerify.cidrs when set.
  • Same eligibility floor as trusted-CIDR approval: fresh scopeless node pairing only; upgrades, browsers, Control UI, and WebChat always prompt.

While a probe runs, the node client is instructed to keep retrying (wait_then_retry) rather than pausing for manual approval; if the probe fails, the next attempt falls back to the normal prompt flow. Failed targets receive a short cooldown (5 minutes after a key mismatch).

Approved devices record approvedVia: "ssh-verified" and their first declared capability surface is approved in the same step, since the key match already demonstrates the node runs under the operator's account on a machine they own, which is the same claim a manual capability approval makes. Later surface upgrades still prompt.

Harden or disable:

{
  gateway: {
    nodes: {
      pairing: {
        // Disable entirely:
        sshVerify: false,
        // ...or scope/tune the probe:
        // sshVerify: { user: "me", identity: "~/.ssh/probe", timeoutMs: 7000, cidrs: ["10.0.0.0/8"] },
      },
    },
  },
}

Auto-approval (macOS app)

The macOS app can attempt a silent approval of node capability requests when:

  • the request is marked silent (the gateway marks the first capability surface silent when device pairing was approved non-interactively), 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.

Trusted-CIDR device auto-approval

WS device pairing for role: node remains 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:

  • Disabled when gateway.nodes.pairing.autoApproveCidrs is unset.
  • No blanket LAN or private-network auto-approve mode exists; SSH-verified auto-approval (above) requires a cryptographic device-key match, never network locality alone.
  • Only a fresh role: node device pairing request with no requested scopes is eligible.
  • Operator, browser, Control UI, and WebChat clients stay manual.
  • Role, scope, metadata, and public-key upgrades stay manual.
  • Same-host loopback trusted-proxy header paths are not eligible, because that path can be spoofed by local callers.

Silent pairing supersede cleanup

Non-interactive approvals record their provenance on the paired-device row: same-host local policy approvals as silent, trusted-CIDR node approvals as trusted-cidr, SSH-verified node approvals as ssh-verified. Clients whose state directory is ephemeral (temporary homes, containers, per-run sandboxes) mint a fresh device keypair per run, and every run silently re-pairs as a brand-new device, without cleanup the paired list grows one stale row per run.

When the Gateway silently approves a local device pairing, it retires older silent-approved records that belong to the same client cluster (matching clientId, clientMode, and display name) and are not currently connected. Local clients run on the gateway host itself, so the cluster key cannot match a different machine. Retired rows lose their tokens immediately; any matching legacy node pairing entry is cleared and a node.pair.resolved removal event is broadcast.

Boundaries:

  • Only records whose latest approval was same-host local (silent) are eligible, as trigger and as target. Trusted-CIDR and SSH-verified pairings cross hosts where display metadata is not a machine identity, so they are never removed automatically, use the Control UI cleanup or openclaw nodes remove for those.
  • Owner-approved and QR/setup-code (bootstrap) pairings are never removed automatically. Records approved before provenance existed stay protected, even after a later silent re-approval of the same device id.
  • Currently connected devices are skipped, so concurrent local sessions with separate state directories keep their tokens while live. Records approved within the last minute are also skipped, so simultaneous pairing handshakes cannot retire each other before their connections register.
  • Affected clients are local by construction, so they re-pair silently on their next connection.

Metadata-upgrade auto-approval

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.

QR pairing helpers

/pair qr turns the pairing payload into structured media, which lets mobile and browser clients scan it directly.

Cleaning up a device also removes any stale pending pairing requests tied to that device id, so nodes pending avoids showing orphaned rows after a revoke.

Locality and forwarded headers

Gateway pairing marks a connection as loopback only when both the raw socket and any upstream proxy evidence line up. When a request hits loopback but includes Forwarded, any X-Forwarded-*, or X-Real-IP header evidence, that forwarded-header data cancels out the loopback locality claim, forcing the pairing path to demand explicit approval instead of quietly treating the request as a same-host connect. For the matching rule on operator auth, check Trusted Proxy Auth.

Storage (local, private)

Pairing state is kept on the paired device records in the shared SQLite state database, located under the Gateway state directory (default ~/.openclaw):

  • ~/.openclaw/state/openclaw.sqlite (paired devices with device auth, approved node surfaces, pending surface requests, pending device pairing requests, and bootstrap tokens)

If you set OPENCLAW_STATE_DIR to something else, the database follows that path. Gateways upgraded from releases that used JSON stores import those at startup and leave devices/*.json.migrated and nodes/*.json.migrated archives behind.

Security notes:

  • Device tokens are secrets; handle the state database as sensitive.
  • Rotating a device token relies on openclaw devices rotate / device.token.rotate.

Transport behavior

  • The transport is stateless; it holds no membership data.
  • Nodes cannot pair when the Gateway is offline or pairing is turned off.
  • In remote mode, pairing targets the remote Gateway's store.
2,341 words · updated Aug 13, 2026