fix(approvals): gate /approve by gateway scopes

This commit is contained in:
Armin Ronacher
2026-02-02 11:51:42 +01:00
committed by Peter Steinberger
parent 66d8117d44
commit efe2a464af
4 changed files with 41 additions and 1 deletions

View File

@@ -79,4 +79,23 @@ describe("/approve command", () => {
}),
);
});
it("rejects gateway clients without approvals scope", async () => {
const cfg = {
commands: { text: true },
} as OpenClawConfig;
const params = buildParams("/approve abc allow-once", cfg, {
Provider: "webchat",
Surface: "webchat",
GatewayClientScopes: ["operator.write"],
});
const mockCallGateway = vi.mocked(callGateway);
mockCallGateway.mockResolvedValueOnce({ ok: true });
const result = await handleCommands(params);
expect(result.shouldContinue).toBe(false);
expect(result.reply?.text).toContain("requires operator.approvals");
expect(mockCallGateway).not.toHaveBeenCalled();
});
});

View File

@@ -1,7 +1,11 @@
import type { CommandHandler } from "./commands-types.js";
import { callGateway } from "../../gateway/call.js";
import { logVerbose } from "../../globals.js";
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../../utils/message-channel.js";
import {
GATEWAY_CLIENT_MODES,
GATEWAY_CLIENT_NAMES,
isInternalMessageChannel,
} from "../../utils/message-channel.js";
const COMMAND = "/approve";
@@ -82,6 +86,20 @@ export const handleApproveCommand: CommandHandler = async (params, allowTextComm
return { shouldContinue: false, reply: { text: parsed.error } };
}
if (isInternalMessageChannel(params.command.channel)) {
const scopes = params.ctx.GatewayClientScopes ?? [];
const hasApprovals = scopes.includes("operator.approvals") || scopes.includes("operator.admin");
if (!hasApprovals) {
logVerbose("Ignoring /approve from gateway client missing operator.approvals.");
return {
shouldContinue: false,
reply: {
text: "❌ /approve requires operator.approvals for gateway clients.",
},
};
}
}
const resolvedBy = buildResolvedByLabel(params);
try {
await callGateway({

View File

@@ -101,6 +101,8 @@ export type MsgContext = {
CommandAuthorized?: boolean;
CommandSource?: "text" | "native";
CommandTargetSessionKey?: string;
/** Gateway client scopes when the message originates from the gateway. */
GatewayClientScopes?: string[];
/** Thread identifier (Telegram topic id or Matrix thread event id). */
MessageThreadId?: string | number;
/** Telegram forum supergroup marker. */

View File

@@ -470,6 +470,7 @@ export const chatHandlers: GatewayRequestHandlers = {
SenderId: clientInfo?.id,
SenderName: clientInfo?.displayName,
SenderUsername: clientInfo?.displayName,
GatewayClientScopes: client?.connect?.scopes,
};
const agentId = resolveSessionAgentId({