


























@@ -214,6 +214,18 @@ export function registerControlUiAndPairingSuite(): void {
214214await writeJson(pairedPath, paired);
215215};
216216217+const injectMalformedPairedAccessLists = async (deviceId: string) => {
218+const { resolvePairingPaths, tryReadJson } = await import("../infra/pairing-files.js");
219+const { writeJson } = await import("../infra/json-files.js");
220+const { pairedPath } = resolvePairingPaths(undefined, "devices");
221+const paired = (await tryReadJson<Record<string, Record<string, unknown>>>(pairedPath)) ?? {};
222+const metadata = getRequiredPairedMetadata(paired, deviceId);
223+metadata.roles = ["operator", null, 42, ""];
224+metadata.scopes = ["operator.read", null, 42, ""];
225+metadata.approvedScopes = ["operator.read", null, 42, ""];
226+await writeJson(pairedPath, paired);
227+};
228+217229const seedApprovedOperatorReadPairing = async (params: {
218230identityPrefix: string;
219231clientId: string;
@@ -964,6 +976,45 @@ export function registerControlUiAndPairingSuite(): void {
964976restoreGatewayToken(prevToken);
965977});
966978979+test("returns pairing-required for malformed persisted access lists", async () => {
980+const { identity, identityPath } = await seedApprovedOperatorReadPairing({
981+identityPrefix: "openclaw-device-malformed-access-",
982+clientId: TEST_OPERATOR_CLIENT.id,
983+clientMode: TEST_OPERATOR_CLIENT.mode,
984+displayName: "malformed-access-upgrade",
985+platform: TEST_OPERATOR_CLIENT.platform,
986+});
987+await injectMalformedPairedAccessLists(identity.deviceId);
988+989+const { server, port, prevToken } = await startControlUiServer("secret");
990+let ws: WebSocket | undefined;
991+try {
992+ws = await openWs(port);
993+const nonce = await readConnectChallengeNonce(ws);
994+const result = await connectReq(ws, {
995+token: "secret",
996+scopes: ["operator.admin"],
997+client: { ...TEST_OPERATOR_CLIENT },
998+device: await buildSignedDeviceForIdentity({
999+ identityPath,
1000+client: TEST_OPERATOR_CLIENT,
1001+scopes: ["operator.admin"],
1002+ nonce,
1003+}),
1004+});
1005+1006+expect(result.ok).toBe(false);
1007+expect(result.error?.message ?? "").toContain("pairing required");
1008+expect((result.error?.details as { reason?: string } | undefined)?.reason).toBe(
1009+"scope-upgrade",
1010+);
1011+} finally {
1012+ws?.close();
1013+await server.close();
1014+restoreGatewayToken(prevToken);
1015+}
1016+});
1017+9671018test("does not expose approved access when a paired device id reconnects with a different key", async () => {
9681019const { identity, identityPath } = await seedApprovedOperatorReadPairing({
9691020identityPrefix: "openclaw-device-key-mismatch-",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。