diff --git a/src/gateway/server/ws-connection/message-handler.ts b/src/gateway/server/ws-connection/message-handler.ts index 41d8234f45b..3abd47dde54 100644 --- a/src/gateway/server/ws-connection/message-handler.ts +++ b/src/gateway/server/ws-connection/message-handler.ts @@ -538,15 +538,19 @@ export function attachGatewayWsMessageHandler(params: { // Shared token/password auth can bypass pairing for trusted operators, but // device-less backend clients must not self-declare scopes. Control UI // keeps its explicitly allowed device-less scopes on the allow path. - // When allowing device-less token auth, grant operator.read so read RPCs work. - // Restrict read-scope fallback to token/password auth only; trusted-proxy - // sessions must not gain read scope without a bound device identity. + // When allowing device-less token/password auth, normalize to operator.read + // so read RPCs work. Do not auto-grant read for trusted-proxy (proxy proves + // identity but must not upgrade scope); preserve explicit scopes there. const grantReadForTokenAuth = decision.kind === "allow" && sharedAuthOk && (authMethod === "token" || authMethod === "password"); - if (!device && (!isControlUi || decision.kind !== "allow" || trustedProxyAuthOk)) { - clearUnboundScopes(grantReadForTokenAuth); + if (!device) { + if (decision.kind !== "allow") { + clearUnboundScopes(false); + } else if (!isControlUi && grantReadForTokenAuth) { + clearUnboundScopes(true); + } } if (decision.kind === "allow") { return true;