Require admin scope for node device token management [AI] (#81067)

* fix: require admin for node device token management

* addressing review-skill

* addressing review-skill

* addressing review-skill

* addressing review-skill

* addressing claude review

* addressing ci

* docs: add changelog entry for PR merge
This commit is contained in:
Pavan Kumar Gondhi
2026-05-13 07:20:35 +05:30
committed by GitHub
parent e2965b5f96
commit 1c85eff9b1
5 changed files with 563 additions and 18 deletions
+1
View File
@@ -6,6 +6,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Require admin scope for node device token management [AI]. (#81067) Thanks @pgondhi987.
- Restrict chat sender allowlist matching [AI]. (#80898) Thanks @pgondhi987.
- Sessions: redact persisted tool result detail metadata before writing transcripts so diagnostic secrets do not survive tool output redaction. (#80444) Thanks @nimbleenigma.
- Codex migration: make Enter activate the highlighted checkbox row before continuing, so `Skip for now` and bulk-selection rows work even when planned items start preselected.
+149 -7
View File
@@ -181,6 +181,40 @@ describe("deviceHandlers", () => {
);
});
it("rejects removing mixed-role devices without admin scope", async () => {
getPairedDeviceMock.mockResolvedValue({
deviceId: "device-1",
role: "operator",
roles: ["operator", "node"],
tokens: {
operator: {
token: "operator-token",
role: "operator",
scopes: ["operator.pairing"],
createdAtMs: 100,
},
node: {
token: "node-token",
role: "node",
scopes: [],
createdAtMs: 100,
revokedAtMs: 200,
},
},
});
const opts = createOptions(
"device.pair.remove",
{ deviceId: "device-1" },
{ client: createClient(["operator.pairing"], "device-1", { isDeviceTokenAuth: true }) },
);
await deviceHandlers["device.pair.remove"](opts);
expect(removePairedDeviceMock).not.toHaveBeenCalled();
expect(opts.context.disconnectClientsForDevice).not.toHaveBeenCalled();
expectRespondedErrorMessage(opts, "device pairing removal denied");
});
it("disconnects active clients after revoking a device token", async () => {
revokeDeviceTokenMock.mockResolvedValue({
ok: true,
@@ -234,6 +268,20 @@ describe("deviceHandlers", () => {
);
});
it("rejects revoking node tokens without admin scope", async () => {
const opts = createOptions(
"device.token.revoke",
{ deviceId: "device-1", role: "node" },
{ client: createClient(["operator.pairing"], "device-1", { isDeviceTokenAuth: true }) },
);
await deviceHandlers["device.token.revoke"](opts);
expect(revokeDeviceTokenMock).not.toHaveBeenCalled();
expect(opts.context.disconnectClientsForDevice).not.toHaveBeenCalled();
expectRespondedErrorMessage(opts, "device token revocation denied");
});
it("treats normalized device ids as self-owned for token revocation", async () => {
revokeDeviceTokenMock.mockResolvedValue({
ok: true,
@@ -336,6 +384,65 @@ describe("deviceHandlers", () => {
);
});
it("allows pairing-scoped device sessions to manage their own operator token", async () => {
rotateDeviceTokenMock.mockResolvedValue({
ok: true,
entry: {
token: "rotated-token",
role: "operator",
scopes: ["operator.pairing"],
createdAtMs: 456,
rotatedAtMs: 789,
},
});
revokeDeviceTokenMock.mockResolvedValue({
ok: true,
entry: { role: "operator", revokedAtMs: 987 },
});
const rotateOpts = createOptions(
"device.token.rotate",
{ deviceId: "device-1", role: "operator", scopes: ["operator.pairing"] },
{ client: createClient(["operator.pairing"], "device-1", { isDeviceTokenAuth: true }) },
);
const revokeOpts = createOptions(
"device.token.revoke",
{ deviceId: "device-1", role: "operator" },
{ client: createClient(["operator.pairing"], "device-1", { isDeviceTokenAuth: true }) },
);
await deviceHandlers["device.token.rotate"](rotateOpts);
await deviceHandlers["device.token.revoke"](revokeOpts);
expect(rotateDeviceTokenMock).toHaveBeenCalledWith({
deviceId: "device-1",
role: "operator",
scopes: ["operator.pairing"],
callerScopes: ["operator.pairing"],
});
expect(revokeDeviceTokenMock).toHaveBeenCalledWith({
deviceId: "device-1",
role: "operator",
callerScopes: ["operator.pairing"],
});
expect(rotateOpts.respond).toHaveBeenCalledWith(
true,
{
deviceId: "device-1",
role: "operator",
token: "rotated-token",
scopes: ["operator.pairing"],
rotatedAtMs: 789,
},
undefined,
);
expect(revokeOpts.respond).toHaveBeenCalledWith(
true,
{ deviceId: "device-1", role: "operator", revokedAtMs: 987 },
undefined,
);
});
it("omits rotated tokens when an admin rotates another device token", async () => {
mockPairedOperatorDevice();
mockRotateOperatorTokenSuccess();
@@ -368,8 +475,27 @@ describe("deviceHandlers", () => {
});
it("rejects rotating a token for a role that was never approved", async () => {
mockPairedOperatorDevice();
rotateDeviceTokenMock.mockResolvedValue({ ok: false, reason: "unknown-device-or-role" });
const opts = createOptions(
"device.token.rotate",
{ deviceId: "device-1", role: "node" },
{ client: createClient(["operator.admin"], "admin-device", { isDeviceTokenAuth: true }) },
);
await deviceHandlers["device.token.rotate"](opts);
expect(rotateDeviceTokenMock).toHaveBeenCalledWith({
deviceId: "device-1",
role: "node",
scopes: undefined,
callerScopes: ["operator.admin"],
});
expect(opts.context.disconnectClientsForDevice).not.toHaveBeenCalled();
expectRespondedErrorMessage(opts, "device token rotation denied");
});
it("rejects rotating node tokens without admin scope", async () => {
mockPairedOperatorDevice();
const opts = createOptions(
"device.token.rotate",
{
@@ -387,12 +513,7 @@ describe("deviceHandlers", () => {
await deviceHandlers["device.token.rotate"](opts);
expect(rotateDeviceTokenMock).toHaveBeenCalledWith({
deviceId: "device-1",
role: "node",
scopes: undefined,
callerScopes: ["operator.pairing"],
});
expect(rotateDeviceTokenMock).not.toHaveBeenCalled();
expect(opts.context.disconnectClientsForDevice).not.toHaveBeenCalled();
expectRespondedErrorMessage(opts, "device token rotation denied");
});
@@ -688,6 +809,27 @@ describe("deviceHandlers", () => {
);
});
it("rejects approving node roles for the caller device without admin scope", async () => {
getPendingDevicePairingMock.mockResolvedValue({
requestId: "req-1",
deviceId: " device-1 ",
publicKey: "pk-1",
role: "node",
roles: ["node"],
ts: 100,
});
const opts = createOptions(
"device.pair.approve",
{ requestId: "req-1" },
{ client: createClient(["operator.pairing"], "device-1", { isDeviceTokenAuth: true }) },
);
await deviceHandlers["device.pair.approve"](opts);
expect(approveDevicePairingMock).not.toHaveBeenCalled();
expectRespondedErrorMessage(opts, "device pairing approval denied");
});
it("rejects rejecting another device from a non-admin device session", async () => {
getPendingDevicePairingMock.mockResolvedValue({
requestId: "req-2",
+113 -2
View File
@@ -1,6 +1,7 @@
import {
approveDevicePairing,
formatDevicePairingForbiddenMessage,
getPairedDevice,
getPendingDevicePairing,
listDevicePairing,
removePairedDevice,
@@ -55,7 +56,11 @@ function logDeviceTokenRotationDenied(params: {
log: { warn: (message: string) => void };
deviceId: string;
role: string;
reason: RotateDeviceTokenDenyReason | "unknown-device-or-role" | "device-ownership-mismatch";
reason:
| RotateDeviceTokenDenyReason
| "unknown-device-or-role"
| "device-ownership-mismatch"
| "role-management-requires-admin";
scope?: string | null;
}) {
const suffix = params.scope ? ` scope=${params.scope}` : "";
@@ -68,7 +73,10 @@ function logDeviceTokenRevocationDenied(params: {
log: { warn: (message: string) => void };
deviceId: string;
role: string;
reason: RevokeDeviceTokenDenyReason | "device-ownership-mismatch";
reason:
| RevokeDeviceTokenDenyReason
| "device-ownership-mismatch"
| "role-management-requires-admin";
scope?: string | null;
}) {
const suffix = params.scope ? ` scope=${params.scope}` : "";
@@ -113,6 +121,56 @@ function shouldReturnRotatedDeviceToken(authz: DeviceManagementAuthz): boolean {
return Boolean(authz.callerDeviceId && authz.callerDeviceId === authz.normalizedTargetDeviceId);
}
function deniesDeviceTokenRoleManagement(
authz: DeviceManagementAuthz,
targetRole: string,
): boolean {
const normalizedTargetRole = targetRole.trim();
if (!normalizedTargetRole || authz.isAdminCaller) {
return false;
}
return normalizedTargetRole !== "operator";
}
function hasNonOperatorDeviceRole(input: { role?: string; roles?: string[] }): boolean {
const roles = new Set<string>();
const role = input.role?.trim();
if (role) {
roles.add(role);
}
for (const entry of input.roles ?? []) {
const normalized = entry.trim();
if (normalized) {
roles.add(normalized);
}
}
return [...roles].some((entry) => entry !== "operator");
}
function hasNonOperatorDeviceTokenRole(
tokens: Record<string, DeviceAuthToken> | undefined,
): boolean {
for (const token of Object.values(tokens ?? {})) {
const normalized = token.role.trim();
if (normalized && normalized !== "operator") {
return true;
}
}
return false;
}
function requestsNonOperatorDeviceRole(pending: { role?: string; roles?: string[] }): boolean {
return hasNonOperatorDeviceRole(pending);
}
function pairedDeviceHasNonOperatorRole(device: {
role?: string;
roles?: string[];
tokens?: Record<string, DeviceAuthToken>;
}): boolean {
return hasNonOperatorDeviceRole(device) || hasNonOperatorDeviceTokenRole(device.tokens);
}
export const deviceHandlers: GatewayRequestHandlers = {
"device.pair.list": async ({ params, respond, client }) => {
if (!validateDevicePairListParams(params)) {
@@ -185,6 +243,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) {
@@ -296,6 +365,20 @@ export const deviceHandlers: GatewayRequestHandlers = {
);
return;
}
if (authz.callerDeviceId && !authz.isAdminCaller) {
const paired = await getPairedDevice(authz.normalizedTargetDeviceId);
if (paired && pairedDeviceHasNonOperatorRole(paired)) {
context.logGateway.warn(
`device pairing removal denied device=${deviceId} reason=role-management-requires-admin`,
);
respond(
false,
undefined,
errorShape(ErrorCodes.INVALID_REQUEST, "device pairing removal denied"),
);
return;
}
}
const removed = await removePairedDevice(deviceId);
if (!removed) {
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "unknown deviceId"));
@@ -341,6 +424,20 @@ export const deviceHandlers: GatewayRequestHandlers = {
);
return;
}
if (deniesDeviceTokenRoleManagement(authz, role)) {
logDeviceTokenRotationDenied({
log: context.logGateway,
deviceId,
role,
reason: "role-management-requires-admin",
});
respond(
false,
undefined,
errorShape(ErrorCodes.INVALID_REQUEST, DEVICE_TOKEN_ROTATION_DENIED_MESSAGE),
);
return;
}
const rotated = await rotateDeviceToken({
deviceId,
role,
@@ -408,6 +505,20 @@ export const deviceHandlers: GatewayRequestHandlers = {
);
return;
}
if (deniesDeviceTokenRoleManagement(authz, role)) {
logDeviceTokenRevocationDenied({
log: context.logGateway,
deviceId,
role,
reason: "role-management-requires-admin",
});
respond(
false,
undefined,
errorShape(ErrorCodes.INVALID_REQUEST, DEVICE_TOKEN_REVOCATION_DENIED_MESSAGE),
);
return;
}
const revoked = await revokeDeviceToken({ deviceId, role, callerScopes: authz.callerScopes });
if (!revoked.ok) {
logDeviceTokenRevocationDenied({
@@ -1,14 +1,20 @@
import { describe, expect, test } from "vitest";
import { WebSocket } from "ws";
import { getPairedDevice } from "../infra/device-pairing.js";
import {
approveDevicePairing,
getPairedDevice,
requestDevicePairing,
} from "../infra/device-pairing.js";
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js";
import { GatewayClient } from "./client.js";
import {
issueOperatorToken,
loadDeviceIdentity,
openTrackedWs,
pairDeviceIdentity,
resolveDeviceIdentityPath,
} from "./device-authz.test-helpers.js";
import { connectGatewayClient } from "./test-helpers.e2e.js";
import {
connectOk,
installGatewayTestHooks,
@@ -137,6 +143,48 @@ async function issuePairingScopedTokenForAdminApprovedDevice(name: string): Prom
};
}
async function issueMixedRolePairingScopedDevice(
name: string,
opts?: { platform?: string },
): Promise<{
deviceId: string;
identityPath: string;
identity: ReturnType<typeof loadDeviceIdentity>["identity"];
pairingToken: string;
publicKey: string;
}> {
const loaded = loadDeviceIdentity(name);
const request = await requestDevicePairing({
deviceId: loaded.identity.deviceId,
publicKey: loaded.publicKey,
role: "operator",
roles: ["operator", "node"],
scopes: ["operator.pairing"],
...(opts?.platform ? { platform: opts.platform } : {}),
clientId: GATEWAY_CLIENT_NAMES.TEST,
clientMode: GATEWAY_CLIENT_MODES.TEST,
});
const approved = await approveDevicePairing(request.request.requestId, {
callerScopes: ["operator.pairing"],
});
expect(approved?.status).toBe("approved");
if (approved?.status !== "approved") {
throw new Error("expected mixed-role device approval");
}
const pairingToken = approved.device.tokens?.operator?.token;
if (!pairingToken) {
throw new Error(`expected operator token for paired device ${loaded.identity.deviceId}`);
}
expect(approved.device.tokens?.node?.token).toBeTypeOf("string");
return {
deviceId: loaded.identity.deviceId,
identityPath: loaded.identityPath,
identity: loaded.identity,
pairingToken,
publicKey: loaded.publicKey,
};
}
describe("gateway device.token.rotate/revoke ownership guard (IDOR)", () => {
test("rejects a device-token caller rotating or revoking another device's token", async () => {
const started = await startServer("secret");
@@ -219,6 +267,244 @@ describe("gateway device.token.rotate/revoke ownership guard (IDOR)", () => {
started.envSnapshot.restore();
}
});
test("rejects a pairing-scoped operator session rotating a revoked node token", async () => {
const started = await startServerWithClient("secret");
const device = await issueMixedRolePairingScopedDevice("same-device-node-token-rotate");
let pairingWs: WebSocket | undefined;
try {
await connectOk(started.ws);
const revoke = await rpcReq<{ revokedAtMs?: number }>(started.ws, "device.token.revoke", {
deviceId: device.deviceId,
role: "node",
});
expect(revoke.ok).toBe(true);
expect(revoke.payload?.revokedAtMs).toBeTypeOf("number");
const pairedAfterRevoke = await getPairedDevice(device.deviceId);
const revokedNodeToken = pairedAfterRevoke?.tokens?.node;
expect(revokedNodeToken?.revokedAtMs).toBeTypeOf("number");
pairingWs = await connectPairingScopedOperator({
port: started.port,
identityPath: device.identityPath,
deviceToken: device.pairingToken,
});
const rotate = await rpcReq(pairingWs, "device.token.rotate", {
deviceId: device.deviceId,
role: "node",
});
expect(rotate.ok).toBe(false);
expect(rotate.error?.message).toBe("device token rotation denied");
const pairedAfterRotate = await getPairedDevice(device.deviceId);
expect(pairedAfterRotate?.tokens?.node?.token).toBe(revokedNodeToken?.token);
expect(pairedAfterRotate?.tokens?.node?.revokedAtMs).toBe(revokedNodeToken?.revokedAtMs);
} finally {
pairingWs?.close();
started.ws.close();
await started.server.close();
started.envSnapshot.restore();
}
});
test("rejects a pairing-scoped operator session approving a refreshed node token", async () => {
const started = await startServerWithClient("secret");
const device = await issueMixedRolePairingScopedDevice("same-device-node-pair-approve");
let pairingWs: WebSocket | undefined;
try {
await connectOk(started.ws);
const revoke = await rpcReq<{ revokedAtMs?: number }>(started.ws, "device.token.revoke", {
deviceId: device.deviceId,
role: "node",
});
expect(revoke.ok).toBe(true);
const pairedAfterRevoke = await getPairedDevice(device.deviceId);
const revokedNodeToken = pairedAfterRevoke?.tokens?.node;
expect(revokedNodeToken?.revokedAtMs).toBeTypeOf("number");
const request = await requestDevicePairing({
deviceId: device.deviceId,
publicKey: device.publicKey,
role: "node",
clientId: GATEWAY_CLIENT_NAMES.NODE_HOST,
clientMode: GATEWAY_CLIENT_MODES.NODE,
});
pairingWs = await connectPairingScopedOperator({
port: started.port,
identityPath: device.identityPath,
deviceToken: device.pairingToken,
});
const approve = await rpcReq(pairingWs, "device.pair.approve", {
requestId: request.request.requestId,
});
expect(approve.ok).toBe(false);
expect(approve.error?.message).toBe("device pairing approval denied");
const pairedAfterApprove = await getPairedDevice(device.deviceId);
expect(pairedAfterApprove?.tokens?.node?.token).toBe(revokedNodeToken?.token);
expect(pairedAfterApprove?.tokens?.node?.revokedAtMs).toBe(revokedNodeToken?.revokedAtMs);
} finally {
pairingWs?.close();
started.ws.close();
await started.server.close();
started.envSnapshot.restore();
}
});
test("rejects local node reconnect after node token revocation", async () => {
const started = await startServerWithClient("secret");
const device = await issueMixedRolePairingScopedDevice("same-device-node-reconnect");
try {
await connectOk(started.ws);
const revoke = await rpcReq<{ revokedAtMs?: number }>(started.ws, "device.token.revoke", {
deviceId: device.deviceId,
role: "node",
});
expect(revoke.ok).toBe(true);
const pairedAfterRevoke = await getPairedDevice(device.deviceId);
const revokedNodeToken = pairedAfterRevoke?.tokens?.node;
expect(revokedNodeToken?.revokedAtMs).toBeTypeOf("number");
await expect(
connectGatewayClient({
url: `ws://127.0.0.1:${started.port}`,
token: "secret",
role: "node",
clientName: GATEWAY_CLIENT_NAMES.NODE_HOST,
clientDisplayName: "node-token-revoked",
clientVersion: "1.0.0",
platform: "linux",
mode: GATEWAY_CLIENT_MODES.NODE,
scopes: [],
commands: ["system.run"],
deviceIdentity: device.identity,
timeoutMessage: "timeout waiting for revoked node reconnect",
}),
).rejects.toThrow("role upgrade pending approval");
const pairedAfterReconnect = await getPairedDevice(device.deviceId);
expect(pairedAfterReconnect?.tokens?.node?.token).toBe(revokedNodeToken?.token);
expect(pairedAfterReconnect?.tokens?.node?.revokedAtMs).toBe(revokedNodeToken?.revokedAtMs);
} finally {
started.ws.close();
await started.server.close();
started.envSnapshot.restore();
}
});
test("rejects local node reconnect with metadata mismatch after node token revocation", async () => {
const started = await startServerWithClient("secret");
const device = await issueMixedRolePairingScopedDevice("same-device-node-metadata-reconnect", {
platform: "linux",
});
try {
await connectOk(started.ws);
const revoke = await rpcReq<{ revokedAtMs?: number }>(started.ws, "device.token.revoke", {
deviceId: device.deviceId,
role: "node",
});
expect(revoke.ok).toBe(true);
const pairedAfterRevoke = await getPairedDevice(device.deviceId);
const revokedNodeToken = pairedAfterRevoke?.tokens?.node;
expect(pairedAfterRevoke?.platform).toBe("linux");
expect(revokedNodeToken?.revokedAtMs).toBeTypeOf("number");
await expect(
connectGatewayClient({
url: `ws://127.0.0.1:${started.port}`,
token: "secret",
role: "node",
clientName: GATEWAY_CLIENT_NAMES.MACOS_APP,
clientDisplayName: "node-token-metadata-mismatch",
clientVersion: "1.0.0",
platform: "darwin",
mode: GATEWAY_CLIENT_MODES.UI,
scopes: [],
commands: ["system.run"],
deviceIdentity: device.identity,
timeoutMessage: "timeout waiting for metadata mismatch node reconnect",
}),
).rejects.toThrow("device metadata change pending approval");
const pairedAfterReconnect = await getPairedDevice(device.deviceId);
expect(pairedAfterReconnect?.platform).toBe("linux");
expect(pairedAfterReconnect?.tokens?.node?.token).toBe(revokedNodeToken?.token);
expect(pairedAfterReconnect?.tokens?.node?.revokedAtMs).toBe(revokedNodeToken?.revokedAtMs);
} finally {
started.ws.close();
await started.server.close();
started.envSnapshot.restore();
}
});
test("rejects self-removal before local node reconnect after node token revocation", async () => {
const started = await startServerWithClient("secret");
const device = await issueMixedRolePairingScopedDevice("same-device-node-remove-reconnect");
let pairingWs: WebSocket | undefined;
try {
await connectOk(started.ws);
const revoke = await rpcReq<{ revokedAtMs?: number }>(started.ws, "device.token.revoke", {
deviceId: device.deviceId,
role: "node",
});
expect(revoke.ok).toBe(true);
const pairedAfterRevoke = await getPairedDevice(device.deviceId);
const revokedNodeToken = pairedAfterRevoke?.tokens?.node;
expect(revokedNodeToken?.revokedAtMs).toBeTypeOf("number");
pairingWs = await connectPairingScopedOperator({
port: started.port,
identityPath: device.identityPath,
deviceToken: device.pairingToken,
});
const remove = await rpcReq(pairingWs, "device.pair.remove", {
deviceId: device.deviceId,
});
expect(remove.ok).toBe(false);
expect(remove.error?.message).toBe("device pairing removal denied");
await expect(
connectGatewayClient({
url: `ws://127.0.0.1:${started.port}`,
token: "secret",
role: "node",
clientName: GATEWAY_CLIENT_NAMES.NODE_HOST,
clientDisplayName: "node-token-removal-denied",
clientVersion: "1.0.0",
platform: "linux",
mode: GATEWAY_CLIENT_MODES.NODE,
scopes: [],
commands: ["system.run"],
deviceIdentity: device.identity,
timeoutMessage: "timeout waiting for denied removal node reconnect",
}),
).rejects.toThrow("role upgrade pending approval");
const pairedAfterReconnect = await getPairedDevice(device.deviceId);
expect(pairedAfterReconnect?.tokens?.node?.token).toBe(revokedNodeToken?.token);
expect(pairedAfterReconnect?.tokens?.node?.revokedAtMs).toBe(revokedNodeToken?.revokedAtMs);
} finally {
pairingWs?.close();
started.ws.close();
await started.server.close();
started.envSnapshot.restore();
}
});
});
describe("gateway device.token.rotate/revoke caller scope guard", () => {
@@ -1012,14 +1012,19 @@ export function attachGatewayWsMessageHandler(params: GatewayWsMessageHandlerPar
publicKey: devicePublicKey,
});
}
const allowSilentLocalPairing = shouldAllowSilentLocalPairing({
locality: pairingLocality,
hasBrowserOriginHeader,
isControlUi,
isWebchat,
isNativeAppUi,
reason,
});
const allowSilentExistingNonOperatorPairing = !(
existingPairedDevice && role !== "operator"
);
const allowSilentLocalPairing =
allowSilentExistingNonOperatorPairing &&
shouldAllowSilentLocalPairing({
locality: pairingLocality,
hasBrowserOriginHeader,
isControlUi,
isWebchat,
isNativeAppUi,
reason,
});
const allowSilentTrustedCidrsNodePairing = shouldAutoApproveNodePairingFromTrustedCidrs(
{
existingPairedDevice: Boolean(existingPairedDevice),