#17425: fix(gateway): auto-approve scope/role upgrades for already-paired devices
gateway
size: XS
Cluster:
Device Pairing and Gateway Fixes
## Problem
When a CLI version upgrade introduces new scopes (e.g. `operator.approvals`, `operator.pairing`), already-paired devices are rejected with **"pairing required"** because their paired entry lacks the new scopes.
For **LAN-bound gateways** (`gateway.bind: "lan"`), this creates an unrecoverable deadlock:
1. The CLI requests new scopes during the handshake
2. The gateway creates a scope-upgrade pairing request with `silent: false` (non-loopback)
3. The pairing request requires manual approval via `openclaw devices approve`
4. But `openclaw devices approve` itself needs to connect to the gateway…
5. …which also fails with "pairing required" (same scope mismatch)
The only workaround is manually editing `~/.openclaw/devices/paired.json` to add the missing scopes — not something users should need to do.
## Root Cause
The `requirePairing()` closure in the WebSocket handshake handler sets `silent: isLocalClient`, meaning auto-approval only happens for loopback connections. Scope/role upgrades for already-paired remote devices are treated the same as brand-new device pairing requests, even though the device identity has already been cryptographically verified.
## Fix
Set `silent: true` for scope-upgrade and role-upgrade pairing requests when the connecting device is already paired (`_paired != null`). At this point:
- The device public key has been verified against the paired record
- The device signature has been validated
- The device token (if present) has been verified
Auto-approving the upgrade is safe because the device is already trusted. This matches the existing behavior for loopback connections and simply extends it to cover the LAN-bound case.
**New device pairing** (`reason: "not-paired"`) still requires explicit approval for non-local connections, preserving the existing security model.
## Changes
- `src/gateway/server/ws-connection/message-handler.ts`: Compute `silent` based on both `isLocalClient` and whether this is an upgrade for an already-paired device
- Added `reason` to the auto-approval log message for observability
## AI-Assisted
Yes — implemented and reviewed with AI assistance.
## Testing
- CI checks are running for this branch.
## Local Validation
```bash
pnpm build && pnpm check && pnpm test
```
Build passes. Lint passes. 4743/4747 tests pass (4 pre-existing env-isolation failures fixed by #16658).
## Local Validation
```bash
pnpm build && pnpm check
```
Build passes. Lint passes. Note: 4 pre-existing test failures in `provider-usage.auth.normalizes-keys.test.ts` (this is what PR #16658 fixes).
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR fixes a deadlock for LAN-bound gateways where already-paired devices requesting new scopes (e.g., after a CLI upgrade introduces `operator.approvals` or `operator.pairing`) could not connect because the scope-upgrade pairing request required manual approval — but the approval command itself needed the new scopes to connect.
The fix sets `silent: true` (auto-approve) for scope-upgrade and role-upgrade pairing requests when the connecting device is already paired. This is safe because by the time the `requirePairing` closure is reached, the device's public key has been matched against the paired record, its signature has been cryptographically verified, and nonce replay protection is enforced for non-loopback connections. New device pairing (`reason: "not-paired"`) still requires explicit approval for non-local connections, preserving the existing security model.
- **Auto-approve logic**: `silent` is now `isLocalClient || (isUpgrade && _paired != null)` where `isUpgrade` checks for `"scope-upgrade"` or `"role-upgrade"` reasons, and `_paired` is only non-null when the device is already in the paired record with a matching public key
- **Observability**: The auto-approval log message now includes `reason=` for easier debugging
- **No test for the LAN-specific path**: The existing e2e test (`server.auth.e2e.test.ts`) validates scope upgrades but connects via `127.0.0.1`, meaning `isLocalClient` is already `true` — the new `isUpgrade && _paired != null` branch is not exercised by tests
<h3>Confidence Score: 4/5</h3>
- This PR is safe to merge — the change correctly extends auto-approval to already-verified devices while preserving the security boundary for new device pairing.
- Score of 4 reflects that the logic change is correct and well-reasoned: the device identity is fully verified (public key match, cryptographic signature, nonce) before auto-approval triggers. The only gap is the lack of a dedicated test covering the non-loopback (LAN) path — the existing e2e test exercises scope upgrades but via localhost, so it doesn't validate the new `isUpgrade && _paired != null` branch specifically. This is a minor concern, not a blocking issue.
- No files require special attention — the single changed file has been thoroughly reviewed and the security model is preserved.
<sub>Last reviewed commit: 8674543</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#22712: fix(gateway): auto-approve all device pairing for localhost connect...
by NewdlDewdl · 2026-02-21
91.5%
#23708: fix(gateway): auto-approve scope upgrades for loopback clients
by widingmarcus-cyber · 2026-02-22
89.0%
#23690: fix(gateway): subagent sessions fail with pairing required on loopb...
by yinghaosang · 2026-02-22
88.1%
#22280: fix(gateway): silently auto-approve local paired-device scope upgrades
by abhishekp76 · 2026-02-21
88.0%
#22587: fix(gateway): silently auto-approve local paired-device scope upgrades
by abhishekp76 · 2026-02-21
87.9%
#22365: fix(gateway): auto-approve loopback scope upgrades
by AIflow-Labs · 2026-02-21
86.9%
#21622: fix(gateway): include read/write in CLI default operator scopes
by zerone0x · 2026-02-20
85.5%
#21697: fix(gateway): unblock local spawn pairing and gated private-LAN ws
by rjuanluis · 2026-02-20
85.1%
#6846: fix: bridge node.pair.* tools to device pairing store
by cortexuvula · 2026-02-02
84.7%
#22253: fix: auto-approve local loopback pairing for role/scope upgrades
by cjpraia · 2026-02-20
83.7%