← Back to PRs

#22583: fix(gateway): add operator.write to scope hierarchy (#22574)

by lailoo open 2026-02-21 10:04 View on GitHub →
size: S experienced-contributor
## Summary - **Bug**: gateway-client enters a "pairing required" loop when connecting with `operator.write` scope but device has `operator.admin` granted - **Root cause**: `operatorScopeSatisfied()` in `src/shared/operator-scope-compat.ts` only has hierarchy logic for `operator.read`; `operator.write` falls through to strict `granted.has(requestedScope)` which returns false when only `operator.admin` is granted - **Fix**: add `operator.write` hierarchy check so `operator.admin` satisfies `operator.write` requests Fixes #22574 ## Problem The operator scope hierarchy was incomplete. The expected hierarchy is `admin > write > read`, but only `read` had upward resolution: ```typescript // Before: only operator.read checks higher scopes if (requestedScope === OPERATOR_READ_SCOPE) { return granted.has(OPERATOR_READ_SCOPE) || granted.has(OPERATOR_WRITE_SCOPE) || granted.has(OPERATOR_ADMIN_SCOPE); } return granted.has(requestedScope); // operator.write falls through here ``` **Before fix:** Input: `roleScopesAllow({ role: "operator", requestedScopes: ["operator.write"], allowedScopes: ["operator.admin"] })` Output: `false` This caused gateway-client to enter a pairing loop: it requests `operator.write`, the device has `operator.admin`, but the scope check fails, triggering `requirePairing("scope-upgrade")` on every connection attempt. ## Changes - `src/shared/operator-scope-compat.ts` — add hierarchy check for `operator.write` (satisfied by `operator.write` or `operator.admin`) - `src/shared/operator-scope-compat.test.ts` — replace buggy assertion with comprehensive hierarchy tests for `operator.write` and `operator.admin` - `CHANGELOG.md` — add fix entry **After fix:** Input: `roleScopesAllow({ role: "operator", requestedScopes: ["operator.write"], allowedScopes: ["operator.admin"] })` Output: `true` ## Test plan - [x] New test: `operator.write` satisfied by `operator.write` and `operator.admin`, rejected by `operator.read` - [x] New test: `operator.admin` only satisfied by exact `operator.admin` match - [x] All 4 existing tests pass - [x] Format check passes ## Effect on User Experience **Before:** gateway-client with `operator.write` scope enters infinite "pairing required" loop when device has `operator.admin` granted, blocking `sessions_spawn` with (1008) after fresh installs or restarts. **After:** `operator.admin` correctly satisfies `operator.write` requests, gateway-client connects without requiring re-pairing. <!-- greptile_comment --> <h3>Greptile Summary</h3> Fixes an incomplete operator scope hierarchy in `operatorScopeSatisfied()` where `operator.write` requests fell through to strict equality matching instead of checking upward in the `admin > write > read` hierarchy. This caused gateway-client to enter a pairing loop when connecting with `operator.write` scope against a device that had `operator.admin` granted. - Added `operator.write` hierarchy check in `src/shared/operator-scope-compat.ts` so `operator.admin` satisfies `operator.write` requests, matching the existing pattern for `operator.read` - Replaced the test that was asserting the buggy behavior with comprehensive hierarchy tests covering `operator.write` and `operator.admin` scope resolution - Added changelog entry <h3>Confidence Score: 5/5</h3> - This PR is safe to merge — it's a minimal, well-scoped bug fix with comprehensive tests. - The change is a 3-line addition to the scope hierarchy function that follows the exact same pattern as the existing `operator.read` check. The fix is consistent with the already-correct `DEVICE_SCOPE_IMPLICATIONS` map in `device-pairing.ts`. The old test was asserting the buggy behavior and has been correctly replaced with comprehensive hierarchy tests. No new security concerns — the change only makes the authorization check more permissive in the intended direction (higher scopes satisfy lower ones). - No files require special attention. <sub>Last reviewed commit: 908539a</sub> <!-- greptile_other_comments_section --> <!-- /greptile_comment -->

Most Similar PRs