From 8af50b5b4c2a302bc70d5023dc6e01be1fea6c3f Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Tue, 28 Apr 2026 22:03:40 +0530 Subject: [PATCH] fix(commands): preserve owner allowlists for native auth --- .../command-auth.owner-default.test.ts | 25 +++++++++++ src/auto-reply/command-auth.ts | 9 ++-- src/auto-reply/command-control.test.ts | 43 +++++++++++++++++++ 3 files changed, 73 insertions(+), 4 deletions(-) diff --git a/src/auto-reply/command-auth.owner-default.test.ts b/src/auto-reply/command-auth.owner-default.test.ts index dd6f8e350b0..0c20fc07245 100644 --- a/src/auto-reply/command-auth.owner-default.test.ts +++ b/src/auto-reply/command-auth.owner-default.test.ts @@ -99,6 +99,31 @@ describe("senderIsOwner only reflects explicit owner authorization", () => { expect(auth.senderIsOwner).toBe(false); }); + it("does not let native command authorization bypass explicit owner allowlists", () => { + const cfg = { + channels: { telegram: {} }, + commands: { ownerAllowFrom: ["456"] }, + } as OpenClawConfig; + + const ctx = { + Provider: "telegram", + Surface: "telegram", + ChatType: "group", + From: "telegram:group:-100123", + SenderId: "200482621", + CommandSource: "native", + } as MsgContext; + + const auth = resolveCommandAuthorization({ + ctx, + cfg, + commandAuthorized: true, + }); + + expect(auth.senderIsOwner).toBe(false); + expect(auth.isAuthorizedSender).toBe(false); + }); + it("senderIsOwner is true when ownerAllowFrom matches sender", () => { const cfg = { channels: { discord: {} }, diff --git a/src/auto-reply/command-auth.ts b/src/auto-reply/command-auth.ts index 072c6049beb..bf6353b620a 100644 --- a/src/auto-reply/command-auth.ts +++ b/src/auto-reply/command-auth.ts @@ -429,6 +429,7 @@ function resolveOwnerAuthorizationState(params: { function resolveCommandSenderAuthorization(params: { commandAuthorized: boolean; + nativeCommandAuthorized: boolean; isOwnerForCommands: boolean; senderCandidates: string[]; commandsAllowFromList: string[] | null; @@ -450,10 +451,7 @@ function resolveCommandSenderAuthorization(params: { !params.providerResolutionError && (commandsAllowAll || Boolean(matchedCommandsAllowFrom)) ); } - if (params.commandAuthorized) { - return true; - } - return params.isOwnerForCommands; + return params.commandAuthorized && (params.isOwnerForCommands || params.nativeCommandAuthorized); } function isConversationLikeIdentity(value: string): boolean { @@ -707,8 +705,11 @@ export function resolveCommandAuthorization(params: { : ownerAllowlistConfigured ? senderIsOwner : senderIsOwnerByScope || Boolean(matchedCommandOwner); + const nativeCommandAuthorized = + commandAuthorized && ctx.CommandSource === "native" && !ownerAllowlistConfigured; const isAuthorizedSender = resolveCommandSenderAuthorization({ commandAuthorized, + nativeCommandAuthorized, isOwnerForCommands, senderCandidates, commandsAllowFromList, diff --git a/src/auto-reply/command-control.test.ts b/src/auto-reply/command-control.test.ts index 3d274bcc4c4..11f1962667a 100644 --- a/src/auto-reply/command-control.test.ts +++ b/src/auto-reply/command-control.test.ts @@ -202,6 +202,49 @@ describe("resolveCommandAuthorization", () => { expect(auth.isAuthorizedSender).toBe(false); }); + it("allows channel-validated native commands when plugin owner enforcement has no owner allowlist", () => { + setActivePluginRegistry( + createTestRegistry([ + { + pluginId: "discord", + plugin: { + ...createOutboundTestPlugin({ + id: "discord", + outbound: { deliveryMode: "direct" }, + }), + commands: { enforceOwnerForCommands: true }, + config: { + listAccountIds: () => ["default"], + resolveAccount: () => ({}), + resolveAllowFrom: () => ["*"], + formatAllowFrom, + }, + }, + source: "test", + }, + ]), + ); + const cfg = { + channels: { discord: { allowFrom: ["*"] } }, + } as OpenClawConfig; + + const auth = resolveCommandAuthorization({ + ctx: { + Provider: "discord", + Surface: "discord", + ChatType: "direct", + From: "discord:123", + SenderId: "123", + CommandSource: "native", + } as MsgContext, + cfg, + commandAuthorized: true, + }); + + expect(auth.senderIsOwner).toBe(false); + expect(auth.isAuthorizedSender).toBe(true); + }); + it("uses explicit owner allowlist when allowFrom is empty", () => { const cfg = { commands: { ownerAllowFrom: ["whatsapp:+15551234567"] },