#6770: fix(gateway): protect host-local transport fields from config.patch
gateway
Cluster:
Gateway and macOS Improvements
## Summary
- When a device pairs as a node with operator privileges, it can push its own gateway config via `config.patch`, overwriting `gateway.mode` from `"local"` to `"remote"` on the host — which kills the gateway
- This happens because the macOS client's config has `gateway.mode="remote"` (since from its perspective it connects to the gateway remotely), and the config sync pushes that value onto the host's `openclaw.json`
- Adds `stripProtectedGatewayPaths()` to strip `gateway.mode`, `gateway.remote`, `gateway.bind`, and `gateway.port` from `config.patch` payloads before merging — these are inherently host-specific transport fields
- Non-protected gateway fields (e.g. `gateway.auth`) still pass through normally
- Stripped fields are logged as warnings via `logGateway.warn`
- `config.set` (full replace) and direct CLI/file edits can still change these fields
## Reproduction
1. Run gateway locally with `gateway.mode: "local"`
2. Pair a macOS device that has `gateway.mode: "remote"` in its own config
3. The device's config sync pushes `gateway.mode: "remote"` + `gateway.remote: {...}` via `config.patch`
4. Gateway restarts, sees `mode: "remote"`, refuses to start → all channels (Telegram, WhatsApp) go offline
## Test plan
- [x] Unit tests for `stripProtectedGatewayPaths` (13 tests passing)
- [x] E2e test verifying `config.patch` with `gateway.mode`/`gateway.remote` does not alter the stored config
- [x] Verified non-gateway fields in the same patch are still applied correctly
- [x] Verified patches without gateway keys pass through unchanged
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This PR prevents paired devices from accidentally breaking a host-local gateway by stripping host-specific gateway transport fields (`gateway.mode`, `gateway.remote`, `gateway.bind`, `gateway.port`) out of `config.patch` payloads before applying the JSON merge patch. The change is implemented via a new `stripProtectedGatewayPaths()` helper in `src/config/merge-patch.ts`, integrated into the gateway `config.patch` handler (`src/gateway/server-methods/config.ts`) with warning logs for stripped keys, and covered by new unit tests plus an e2e regression test to ensure non-protected fields in the same patch still apply.
<h3>Confidence Score: 4/5</h3>
- This PR looks safe to merge with minor integration-contract risk around the new `context` dependency in `config.patch`.
- The change is narrowly scoped, covered by unit + e2e tests, and the stripping logic is straightforward. The main risk is a runtime crash if any handler invocation path doesn’t supply `context.logGateway`, since `config.patch` now assumes it exists when logging stripped fields.
- src/gateway/server-methods/config.ts
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#11455: fix(gateway): default gateway.mode to local when unset
by AnonO6 · 2026-02-07
82.3%
#23364: Gateway: add risk-ack interlock for dangerous Control UI flags
by bmendonca3 · 2026-02-22
78.5%
#23361: Gateway: reject scope assertions without identity binding
by bmendonca3 · 2026-02-22
78.2%
#19088: fix(gateway): allow startup with unset mode and fix pairing for local…
by mdanassaif · 2026-02-17
77.7%
#20089: fix(gateway): preserve control-ui scopes when dangerouslyDisableDev...
by vashkartik · 2026-02-18
77.3%
#17705: fix(gateway): allow trusted-proxy auth to bypass device-pairing gates
by dashed · 2026-02-16
77.3%
#22227: fix(security): harden gateway auth — audit logging, pairing, mode v...
by novalis133 · 2026-02-20
77.2%
#4653: fix(gateway): improve crash resilience for mDNS and network errors
by AyedAlmudarra · 2026-01-30
77.1%
#21697: fix(gateway): unblock local spawn pairing and gated private-LAN ws
by rjuanluis · 2026-02-20
76.9%
#19885: test(gateway,browser): isolate tests from ambient OPENCLAW_GATEWAY_...
by NewdlDewdl · 2026-02-18
76.8%