#21697: fix(gateway): unblock local spawn pairing and gated private-LAN ws
gateway
size: M
Cluster:
Device Pairing and Gateway Fixes
## 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
#17425: fix(gateway): auto-approve scope/role upgrades for already-paired d...
by sauerdaniel · 2026-02-15
85.1%
#23690: fix(gateway): subagent sessions fail with pairing required on loopb...
by yinghaosang · 2026-02-22
84.2%
#22712: fix(gateway): auto-approve all device pairing for localhost connect...
by NewdlDewdl · 2026-02-21
83.4%
#22365: fix(gateway): auto-approve loopback scope upgrades
by AIflow-Labs · 2026-02-21
83.4%
#22280: fix(gateway): silently auto-approve local paired-device scope upgrades
by abhishekp76 · 2026-02-21
82.7%
#21265: Security/Pairing: reject insecure non-loopback ws setup URLs
by bmendonca3 · 2026-02-19
82.4%
#23708: fix(gateway): auto-approve scope upgrades for loopback clients
by widingmarcus-cyber · 2026-02-22
82.4%
#22343: fix(gateway): treat private LAN hosts as local direct
by AIflow-Labs · 2026-02-21
81.9%
#22227: fix(security): harden gateway auth — audit logging, pairing, mode v...
by novalis133 · 2026-02-20
81.3%
#22587: fix(gateway): silently auto-approve local paired-device scope upgrades
by abhishekp76 · 2026-02-21
80.9%