diff --git a/CHANGELOG.md b/CHANGELOG.md index 30e7bae6e47..724627e9f8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -229,6 +229,7 @@ Docs: https://docs.openclaw.ai - Auto-reply/subagents: reject `/focus` from leaf subagents and scope fallback target resolution to the requesting subagent's children, so subagents cannot bind conversations outside their control boundary. (#73613) Thanks @drobison00. - Gateway/startup: skip inherited workspace startup memory for sandboxed spawned sessions without real-workspace write access, so `/new` no longer preloads host workspace memory into isolated child runs. (#73611) Thanks @drobison00. - Agents/tool policy: validate caller group IDs against session or spawned context before applying group-scoped tool policies or persisting gateway group metadata, so forged group IDs cannot unlock more permissive tools. (#73720) Thanks @mmaps. +- Commands: keep channel-prefixed owner allowlist entries scoped to matching providers so webchat command contexts cannot inherit external channel owners. Thanks @zsxsoft. ## 2026.4.27 diff --git a/src/auto-reply/command-auth.ts b/src/auto-reply/command-auth.ts index bf6353b620a..d922df87003 100644 --- a/src/auto-reply/command-auth.ts +++ b/src/auto-reply/command-auth.ts @@ -289,7 +289,8 @@ function resolveOwnerAllowFromList(params: { const prefix = trimmed.slice(0, separatorIndex); const channel = normalizeAnyChannelId(prefix); if (channel) { - if (params.providerId && channel !== params.providerId) { + // Channel-prefixed entries require a known matching provider; webchat leaves it unset. + if (!params.providerId || channel !== params.providerId) { continue; } const remainder = trimmed.slice(separatorIndex + 1).trim(); diff --git a/src/auto-reply/command-control.test.ts b/src/auto-reply/command-control.test.ts index 11f1962667a..1125ce90ee4 100644 --- a/src/auto-reply/command-control.test.ts +++ b/src/auto-reply/command-control.test.ts @@ -356,6 +356,88 @@ describe("resolveCommandAuthorization", () => { expect(auth.isAuthorizedSender).toBe(true); }); + it("does not apply channel-prefixed owner wildcards to webchat command contexts", () => { + const cfg = { + commands: { ownerAllowFrom: ["discord:*"] }, + } as OpenClawConfig; + + const auth = resolveCommandAuthorization({ + ctx: { + Provider: "webchat", + Surface: "webchat", + OriginatingChannel: "webchat", + SenderId: "123456789012345678", + GatewayClientScopes: ["operator.write"], + } as MsgContext, + cfg, + commandAuthorized: true, + }); + + expect(auth.providerId).toBeUndefined(); + expect(auth.senderIsOwner).toBe(false); + }); + + it("does not apply channel-prefixed owner identities to webchat command contexts", () => { + const cfg = { + commands: { ownerAllowFrom: ["discord:123456789012345678"] }, + } as OpenClawConfig; + + const auth = resolveCommandAuthorization({ + ctx: { + Provider: "webchat", + Surface: "webchat", + OriginatingChannel: "webchat", + SenderId: "123456789012345678", + GatewayClientScopes: ["operator.write"], + } as MsgContext, + cfg, + commandAuthorized: true, + }); + + expect(auth.providerId).toBeUndefined(); + expect(auth.senderIsOwner).toBe(false); + }); + + it("applies channel-prefixed owner identities to matching providers", () => { + const cfg = { + commands: { ownerAllowFrom: ["discord:123456789012345678"] }, + } as OpenClawConfig; + + const auth = resolveCommandAuthorization({ + ctx: { + Provider: "discord", + Surface: "discord", + From: "discord:123456789012345678", + SenderId: "123456789012345678", + } as MsgContext, + cfg, + commandAuthorized: true, + }); + + expect(auth.providerId).toBe("discord"); + expect(auth.senderIsOwner).toBe(true); + }); + + it("does not apply channel-prefixed owner wildcards to mismatched providers", () => { + const cfg = { + commands: { ownerAllowFrom: ["telegram:*"] }, + } as OpenClawConfig; + + const auth = resolveCommandAuthorization({ + ctx: { + Provider: "discord", + Surface: "discord", + From: "discord:123456789012345678", + SenderId: "123456789012345678", + } as MsgContext, + cfg, + commandAuthorized: true, + }); + + expect(auth.providerId).toBe("discord"); + expect(auth.senderIsOwner).toBe(false); + }); + it("preserves external channel command auth in mixed webchat contexts", () => { const cfg = { commands: { allowFrom: { whatsapp: ["+15551234567"] } },