← Back to PRs

#21697: fix(gateway): unblock local spawn pairing and gated private-LAN ws

by rjuanluis open 2026-02-20 07:49 View on GitHub →
gateway size: M
## Root Cause Two independent guardrails interacted incorrectly and blocked local sub-agent spawning: 1. **Strict scope matching in pairing validation** - The gateway required an exact scope-string match for relay operations. - A session paired with broader admin scope (`operator`) was rejected for narrower relay scopes (`operator.relay.sessions.spawn`), causing `gateway closed (1008): pairing required` on loopback. 2. **Absolute ws:// LAN deny in transport checks** - `ws://` non-loopback targets were blocked unconditionally, even in authenticated local/private development contexts. - That produced `SECURITY ERROR: ws:// non-loopback` for LAN binds. ## Solution Applied - Added scope coverage compatibility (`scopeCovers`) so broader paired scopes satisfy narrower required scopes while still denying true scope upgrades. - Updated pairing checks to use compatibility logic instead of exact string equality. - Kept default-deny for plaintext non-loopback `ws://`, but introduced a **gated override** for private/LAN development: - `OPENCLAW_ALLOW_PLAINTEXT_PRIVATE_WS=1` - only applies to private IP targets - still requires authenticated context. ## Validation Matrix (4 scenarios) | Scenario | Expected | Result | |---|---|---| | a) loopback + auth + sufficient scope | pass | ✅ pass | | b) loopback + insufficient scope | fail | ✅ fail (pairing required) | | c) lan without `OPENCLAW_ALLOW_PLAINTEXT_PRIVATE_WS` | fail | ✅ fail (security error) | | d) lan with flag + auth + private ws | pass | ✅ pass | ### Test Evidence - Unit: `npx vitest run src/shared/operator-scope-compat.test.ts src/infra/device-pairing.test.ts src/gateway/net.test.ts src/gateway/client.test.ts src/gateway/call.test.ts` - E2E: `npx vitest run --config vitest.e2e.config.ts src/gateway/server.auth.e2e.test.ts` - Scenario selectors: - `npx vitest run --config vitest.e2e.config.ts --reporter=verbose src/gateway/server.auth.e2e.test.ts -t "allows operator scopes covered by paired admin scope without re-pairing"` - `npx vitest run --config vitest.e2e.config.ts --reporter=verbose src/gateway/server.auth.e2e.test.ts -t "requires pairing for scope upgrades"` - `npx vitest run --reporter=verbose src/gateway/call.test.ts -t "blocks ws:// to LAN IP without TLS"` - `npx vitest run --reporter=verbose src/gateway/call.test.ts -t "allows ws:// to LAN IP only with explicit private-ws override and auth"` ## Risks / Rollback ### Risks - Scope compatibility changes auth behavior; mitigated by explicit tests for allowed narrowing and denied upgrades. - New LAN override could be misused if enabled broadly; mitigated by opt-in env flag + private IP restriction + authenticated-context requirement. ### Rollback - Revert commit `8f2a24d9d45143ed2c5f2a15d2d766c759e92a69` to restore previous behavior. ## Release Notes - Fix: local loopback relay/session spawn no longer requires invalid re-pairing when existing paired scope already covers requested relay scope. - Security: plaintext `ws://` non-loopback remains blocked by default. - Added explicit development override for authenticated private LAN only: - `OPENCLAW_ALLOW_PLAINTEXT_PRIVATE_WS=1` ## Migration - If you used a manual re-pairing workaround for loopback spawn failures, remove that workaround. - If you used LAN relay in local dev, set `OPENCLAW_ALLOW_PLAINTEXT_PRIVATE_WS=1` only in local/dev environments when needed. - No production config change required. <!-- greptile_comment --> <h3>Greptile Summary</h3> Fixed local pairing flow and added gated LAN websocket support with two independent security improvements: **Scope Compatibility Enhancement** - Replaced strict scope string matching with hierarchical compatibility logic via `roleScopesAllow` - `operator.admin` now properly covers all narrower operator scopes (pairing, approvals, relay, etc.) - `operator.write` satisfies `operator.read` requests (write implies read) - Resolves loopback spawn failures where broader paired scopes were incorrectly rejected for narrower operations **Plaintext Private WebSocket Override** - Added opt-in `OPENCLAW_ALLOW_PLAINTEXT_PRIVATE_WS=1` environment variable for local LAN development - Restricted to: private IP targets only (not hostnames), requires authentication (token/password), only applies to local `bind=lan` mode - Default behavior unchanged: `ws://` to non-loopback remains blocked - Includes comprehensive test coverage for all 4 security scenarios Both changes maintain security defaults while enabling legitimate local development workflows. <h3>Confidence Score: 5/5</h3> - Safe to merge with high confidence - well-tested security enhancements with proper guardrails - The PR introduces two independent security fixes with comprehensive test coverage (unit tests + E2E tests covering all 4 validation scenarios). The scope compatibility logic is sound and addresses a real pairing bug. The plaintext WebSocket override is properly gated with multiple security layers (env flag + auth requirement + private IP restriction + local-only mode). All changes are additive and maintain secure defaults. - No files require special attention <sub>Last reviewed commit: 8f2a24d</sub> <!-- greptile_other_comments_section --> <sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub> <!-- /greptile_comment -->

Most Similar PRs