




















@@ -3,6 +3,7 @@ import { isCommandFlagEnabled } from "../../config/commands.js";
33import type { OpenClawConfig } from "../../config/config.js";
44import type { MsgContext } from "../templating.js";
55import { handleBashChatCommand } from "./bash-command.js";
6+import { requireGatewayClientScope } from "./command-gates.js";
67import { handleConfigCommand, handleDebugCommand } from "./commands-config.js";
78import type { HandleCommandsParams } from "./commands-types.js";
89import { parseInlineDirectives } from "./directive-handling.parse.js";
@@ -458,6 +459,76 @@ describe("command gating", () => {
458459expect(replaceConfigFileMock.mock.calls.length).toBe(previousWriteCount);
459460});
460461462+it("enforces gateway client permissions when the command channel is external", () => {
463+const result = requireGatewayClientScope(
464+{
465+ctx: {
466+Provider: "internal",
467+OriginatingChannel: "telegram",
468+GatewayClientScopes: ["operator.write"],
469+},
470+command: {
471+channel: "telegram",
472+},
473+} as unknown as HandleCommandsParams,
474+{
475+label: "/config write",
476+allowedScopes: ["operator.admin"],
477+missingText: "/config set|unset requires operator.admin for gateway clients.",
478+},
479+);
480+481+expect(result?.shouldContinue).toBe(false);
482+expect(result?.reply?.text).toContain("requires operator.admin");
483+expect(isInternalMessageChannelMock).not.toHaveBeenCalled();
484+});
485+486+it("enforces gateway client permissions when the scope list is empty", () => {
487+const result = requireGatewayClientScope(
488+{
489+ctx: {
490+Provider: "internal",
491+OriginatingChannel: "telegram",
492+GatewayClientScopes: [],
493+},
494+command: {
495+channel: "telegram",
496+},
497+} as unknown as HandleCommandsParams,
498+{
499+label: "/config write",
500+allowedScopes: ["operator.admin"],
501+missingText: "/config set|unset requires operator.admin for gateway clients.",
502+},
503+);
504+505+expect(result?.shouldContinue).toBe(false);
506+expect(result?.reply?.text).toContain("requires operator.admin");
507+expect(isInternalMessageChannelMock).not.toHaveBeenCalled();
508+});
509+510+it("does not require gateway client permissions when scopes are absent", () => {
511+const result = requireGatewayClientScope(
512+{
513+ctx: {
514+Provider: "telegram",
515+OriginatingChannel: "telegram",
516+},
517+command: {
518+channel: "telegram",
519+},
520+} as unknown as HandleCommandsParams,
521+{
522+label: "/config write",
523+allowedScopes: ["operator.admin"],
524+missingText: "/config set|unset requires operator.admin for gateway clients.",
525+},
526+);
527+528+expect(result).toBeNull();
529+expect(isInternalMessageChannelMock).not.toHaveBeenCalled();
530+});
531+461532it("enforces gateway client permissions for /config commands", async () => {
462533const baseCfg = { commands: { config: true, text: true } } as OpenClawConfig;
463534@@ -469,7 +540,6 @@ describe("command gating", () => {
469540blockedParams.command.channelId = "webchat";
470541blockedParams.command.surface = "webchat";
471542blockedParams.command.senderIsOwner = true;
472-isInternalMessageChannelMock.mockReturnValueOnce(true);
473543const blockedResult = await handleConfigCommand(blockedParams, true);
474544expect(blockedResult?.shouldContinue).toBe(false);
475545expect(blockedResult?.reply?.text).toContain("requires operator.admin");
@@ -485,7 +555,7 @@ describe("command gating", () => {
485555showParams.command.channel = "webchat";
486556showParams.command.channelId = "webchat";
487557showParams.command.surface = "webchat";
488-isInternalMessageChannelMock.mockReturnValueOnce(true);
558+showParams.command.senderIsOwner = true;
489559const showResult = await handleConfigCommand(showParams, true);
490560expect(showResult?.shouldContinue).toBe(false);
491561expect(showResult?.reply?.text).toContain("Config messages.ackReaction");
@@ -502,7 +572,6 @@ describe("command gating", () => {
502572setParams.command.channelId = "webchat";
503573setParams.command.surface = "webchat";
504574setParams.command.senderIsOwner = true;
505-isInternalMessageChannelMock.mockReturnValueOnce(true);
506575const setResult = await handleConfigCommand(setParams, true);
507576expect(setResult?.shouldContinue).toBe(false);
508577expect(setResult?.reply?.text).toContain("Config updated");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。