From 05ca581ed0d0a7c863ad26a64fc049edd5c74f98 Mon Sep 17 00:00:00 2001 From: Coy Geek <65363919+coygeek@users.noreply.github.com> Date: Fri, 27 Mar 2026 12:50:20 -0700 Subject: [PATCH] fix: fail closed when pairing scopes are missing --- extensions/device-pair/index.test.ts | 37 ++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/extensions/device-pair/index.test.ts b/extensions/device-pair/index.test.ts index 87ddbc010e3..c5cb04365c0 100644 --- a/extensions/device-pair/index.test.ts +++ b/extensions/device-pair/index.test.ts @@ -772,4 +772,41 @@ describe("device-pair /pair approve", () => { text: "⚠️ This command requires operator.admin to approve this pairing request.", }); }); + + it("fails closed when gateway scopes are absent", async () => { + vi.mocked(listDevicePairing).mockResolvedValueOnce({ + pending: [ + { + requestId: "req-1", + deviceId: "victim-phone", + publicKey: "victim-public-key", + displayName: "Victim Phone", + platform: "ios", + ts: Date.now(), + }, + ], + paired: [], + }); + vi.mocked(approveDevicePairing).mockImplementationOnce(async () => ({ + status: "forbidden", + missingScope: "operator.admin", + })); + + const command = registerPairCommand(); + const result = await command.handler( + createCommandContext({ + channel: "webchat", + args: "approve latest", + commandBody: "/pair approve latest", + gatewayClientScopes: undefined, + }), + ); + + expect(vi.mocked(approveDevicePairing)).toHaveBeenCalledWith("req-1", { + callerScopes: [], + }); + expect(result).toEqual({ + text: "⚠️ This command requires operator.admin to approve this pairing request.", + }); + }); });