#16827: fix: allow device tokens with empty scopes to accept requested scopes
stale
size: XS
trusted-contributor
Cluster:
Device Auth and Security Fixes
## Summary
When a device was paired before the scope feature was added, the stored token may have empty scopes. After the security update (commit cfd112952) that requires explicit scopes, these devices would fail authentication with "missing scope: operator.read" error because the `scopesAllow` function would reject any requested scopes when the allowed scopes are empty.
This change makes `scopesAllow` treat empty allowed scopes as "no restrictions", which is backward-compatible with existing paired devices while still enforcing scope restrictions when scopes are explicitly set.
## Changes
- Modified `scopesAllow` function in `src/infra/device-pairing.ts` to return `true` (allow) when the allowed scopes array is empty, instead of returning `false` (deny)
## Testing
- Ran existing unit tests in `src/infra/device-pairing.test.ts` - all 4 tests pass
- The fix is backward-compatible as it only affects tokens with empty scopes
Fixes openclaw/openclaw#16820
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR fixes a backward compatibility issue where devices paired before the scope feature (with empty `scopes` arrays) were incorrectly rejected after the security hardening update. The fix changes `scopesAllow` to treat empty allowed scopes as "no restrictions" by returning `true` instead of `false`.
**Key Changes:**
- Modified `scopesAllow` in `src/infra/device-pairing.ts:167-175` to return `true` when `allowed.length === 0`
- Added explanatory comment documenting the backward compatibility behavior
**Architectural Note:**
The fix ensures legacy devices continue to work, but tokens with empty scopes will never self-upgrade to carry explicit scopes. The `ensureDeviceToken` function (line 416) will continue returning the existing empty-scope token since `scopesAllow(requestedScopes, [])` now returns `true`. This means these devices will permanently rely on the "empty = allow all" fallback rather than migrating to properly scoped tokens.
This is a reasonable trade-off for backward compatibility, though it means the codebase must indefinitely support the empty-scope behavior.
<h3>Confidence Score: 4/5</h3>
- Safe to merge - fixes critical authentication issue for legacy devices
- The fix correctly restores backward compatibility and unblocks legacy devices. The logic is sound and well-commented. However, scored 4 instead of 5 because: (1) empty-scope tokens will never self-upgrade to explicit scopes, requiring permanent support for this fallback behavior, and (2) test coverage could be expanded to explicitly verify the empty-scope behavior that this PR fixes.
- No files require special attention - the change is minimal and focused
<sub>Last reviewed commit: d6c313e</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#21664: fix(gateway): require re-pairing for legacy devices that lack scope...
by AI-Reviewer-QS · 2026-02-20
83.1%
#17605: fix: preserve scopes when disableControlUiDeviceAuth is enabled
by MisterGuy420 · 2026-02-16
82.1%
#17425: fix(gateway): auto-approve scope/role upgrades for already-paired d...
by sauerdaniel · 2026-02-15
81.1%
#21666: fix(gateway): restrict auto-paired device scopes to safe defaults
by AI-Reviewer-QS · 2026-02-20
79.1%
#22312: Fix legacy paired metadata handling for reconnect scope compatibility
by AIflow-Labs · 2026-02-21
78.8%
#22712: fix(gateway): auto-approve all device pairing for localhost connect...
by NewdlDewdl · 2026-02-21
78.8%
#23690: fix(gateway): subagent sessions fail with pairing required on loopb...
by yinghaosang · 2026-02-22
78.6%
#17195: fix: Add operator.read/write scopes to Dashboard auto-pairing
by MisterGuy420 · 2026-02-15
78.4%
#23039: fix: subagent announce fails with pairing required due to missing o...
by ascott · 2026-02-21
78.4%
#21476: fix(cli): include operator.read in default CLI scopes
by heyrtl · 2026-02-20
78.3%