Bug Summary
macOS/iOS/Control UI operator clients request 5 scopes at connect time, but bootstrap-issued device tokens only allow 4 scopes. The gateway's verifyDeviceToken detects the mismatch but the auth pipeline collapses it into a generic "device token mismatch" error, making it appear the token itself is wrong.
Root Cause
1. Client-server scope gap
Clients request (macOS/iOS/Control UI):
operator.admin
operator.read
operator.write
operator.approvals
operator.pairing
Bootstrap token allowlist (src/shared/device-bootstrap-profile.ts:13-18):
operator.approvals
operator.read
operator.talk.secrets
operator.write
operator.admin and operator.pairing are silently stripped during bootstrap token issuance via resolveBootstrapProfileScopesForRole(), so the stored device token only has 4 scopes. When the client reconnects requesting 5 scopes, verifyDeviceToken() fails.
2. Error masking
verifyDeviceToken() (src/infra/device-pairing.ts:880-928) returns granular reasons including scope-mismatch. However, resolveConnectAuthDecision() (src/gateway/server/ws-connection/auth-context.ts:216-223) collapses ALL device token verification failures into device_token_mismatch:
authResult = {
ok: false,
reason: params.state.deviceTokenCandidateSource === "explicit-device-token"
? "device_token_mismatch"
: (authResult.reason ?? "device_token_mismatch"),
};
This renders as: unauthorized: device token mismatch (rotate/reissue device token) — misleading the user into thinking the token value is wrong.
3. Android is immune
Android only requests ["operator.read", "operator.write", "operator.talk.secrets"] (ConnectionManager.kt:163), all within the bootstrap allowlist.
Affected Files
| File |
Issue |
apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayChannel.swift:131-137 |
Requests 5 scopes including admin/pairing |
apps/macos/Sources/OpenClawMacCLI/GatewayScopes.swift:1-7 |
Same |
ui/src/ui/gateway.ts:154-160 (CONTROL_UI_OPERATOR_SCOPES) |
Same |
src/gateway/server/ws-connection/auth-context.ts:216-223 |
Collapses scope-mismatch to device_token_mismatch |
src/gateway/protocol/connect-error-details.ts |
No scope_mismatch case in error mapping |
src/gateway/server/ws-connection/auth-messages.ts |
No scope-specific error message |
Proposed Fix
A. Client-side (immediate)
Narrow defaultOperatorConnectScopes in Swift/TS to match the bootstrap handoff allowlist — remove operator.admin and operator.pairing.
B. Server-side (better diagnostics)
- Propagate the
scope-mismatch reason through resolveConnectAuthDecision instead of collapsing it
- Add
scope_mismatch to the resolveAuthConnectErrorDetailCode switch
- Add a human-readable message like
unauthorized: device token scope mismatch (re-pair or approve scope upgrade)
Bug Summary
macOS/iOS/Control UI operator clients request 5 scopes at connect time, but bootstrap-issued device tokens only allow 4 scopes. The gateway's
verifyDeviceTokendetects the mismatch but the auth pipeline collapses it into a generic "device token mismatch" error, making it appear the token itself is wrong.Root Cause
1. Client-server scope gap
Clients request (macOS/iOS/Control UI):
operator.adminoperator.readoperator.writeoperator.approvalsoperator.pairingBootstrap token allowlist (
src/shared/device-bootstrap-profile.ts:13-18):operator.approvalsoperator.readoperator.talk.secretsoperator.writeoperator.adminandoperator.pairingare silently stripped during bootstrap token issuance viaresolveBootstrapProfileScopesForRole(), so the stored device token only has 4 scopes. When the client reconnects requesting 5 scopes,verifyDeviceToken()fails.2. Error masking
verifyDeviceToken()(src/infra/device-pairing.ts:880-928) returns granular reasons includingscope-mismatch. However,resolveConnectAuthDecision()(src/gateway/server/ws-connection/auth-context.ts:216-223) collapses ALL device token verification failures intodevice_token_mismatch:This renders as:
unauthorized: device token mismatch (rotate/reissue device token)— misleading the user into thinking the token value is wrong.3. Android is immune
Android only requests
["operator.read", "operator.write", "operator.talk.secrets"](ConnectionManager.kt:163), all within the bootstrap allowlist.Affected Files
apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayChannel.swift:131-137admin/pairingapps/macos/Sources/OpenClawMacCLI/GatewayScopes.swift:1-7ui/src/ui/gateway.ts:154-160(CONTROL_UI_OPERATOR_SCOPES)src/gateway/server/ws-connection/auth-context.ts:216-223scope-mismatchtodevice_token_mismatchsrc/gateway/protocol/connect-error-details.tsscope_mismatchcase in error mappingsrc/gateway/server/ws-connection/auth-messages.tsProposed Fix
A. Client-side (immediate)
Narrow
defaultOperatorConnectScopesin Swift/TS to match the bootstrap handoff allowlist — removeoperator.adminandoperator.pairing.B. Server-side (better diagnostics)
scope-mismatchreason throughresolveConnectAuthDecisioninstead of collapsing itscope_mismatchto theresolveAuthConnectErrorDetailCodeswitchunauthorized: device token scope mismatch (re-pair or approve scope upgrade)