Skip to content

fix(auth): operator scope mismatch silently rejected as device token mismatch #79292

Description

@ranjeshj

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)

  1. Propagate the scope-mismatch reason through resolveConnectAuthDecision instead of collapsing it
  2. Add scope_mismatch to the resolveAuthConnectErrorDetailCode switch
  3. Add a human-readable message like unauthorized: device token scope mismatch (re-pair or approve scope upgrade)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions