Root cause
Authentication & access control
The role-management check for device approvals was added inside the existing caller-device block, so it only ran for non-admin callers that presented a device identifier.
How AI contributed: Flawed AI-written codePublished Jul 17, 2026 in
openclaw/openclawAll 35 findings in this repository
The commit that introduced the rule added a helper that reads the roles a pending pairing asks for, and denies the approval with a role-management-requires-admin reason when any requested role is not operator. That denial was written into the block that already handled ownership, which opens only for a caller that presents a device identifier and is not an administrator, so the new rule inherited the older condition and sessions without a device identifier kept approving node-role pairings. A later commit by a maintainer changes the enclosing branch to test only whether the caller is an administrator, and moves the device-identifier test into the ownership comparison, so the role rule now reaches every non-admin caller while the ownership rule still applies only where it can be evaluated.
A pairing session that carries no caller device identifier, such as one opened with the shared secret or a trusted-proxy session, sends device.pair.approve for a pending request whose requested role is not operator. In the handler the role-management rule sits inside the caller-device condition, so a session without a device identifier never reaches it and the approval proceeds. The caller therefore hands a device a node role that only an administrator should be able to grant.
Root cause
The role-management check for device approvals was added inside the existing caller-device block, so it only ran for non-admin callers that presented a device identifier.
Fix
The enclosing condition now asks only whether the caller is an administrator, and the ownership comparison itself tests for a caller device identifier.
Case facts
@@ -203,6 +218,17 @@ export const deviceHandlers: GatewayRequestHandlers = { ); return; }+ if (requestsNonOperatorDeviceRole(pending)) {+ context.logGateway.warn(+ `device pairing approval denied request=${requestId} reason=role-management-requires-admin`,+ );+ respond(+ false,+ undefined,+ errorShape(ErrorCodes.INVALID_REQUEST, DEVICE_PAIR_APPROVAL_DENIED_MESSAGE),+ );+ return;+ } } const approved = await approveDevicePairing(requestId, { callerScopes: authz.callerScopes }); if (!approved) {@@ -222,7 +222,7 @@ export const deviceHandlers: GatewayRequestHandlers = { } const { requestId } = params as { requestId: string }; const authz = resolveDeviceSessionAuthz(client);- if (authz.callerDeviceId && !authz.isAdminCaller) {+ if (!authz.isAdminCaller) { const pending = await getPendingDevicePairing(requestId); if (!pending) { respond(AI-assisted change 32ea0f0ff2237d91b4ff58952b6c3feb3466a447ce1497e72226d8af740588c2 · Fix 2505c2e2883ca41b624b6257e9ef1aea967ff08b9c1cc0247ebcc9861fa32ba0