fix(security): harden runtime command override gating

This commit is contained in:
Peter Steinberger
2026-02-21 12:49:45 +01:00
parent cb84c537f4
commit fbb79d4013
12 changed files with 149 additions and 13 deletions

View File

@@ -3,6 +3,7 @@ import { getFinishedSession, getSession, markExited } from "../../agents/bash-pr
import { createExecTool } from "../../agents/bash-tools.js";
import { resolveSandboxRuntimeStatus } from "../../agents/sandbox.js";
import { killProcessTree } from "../../agents/shell-utils.js";
import { isCommandFlagEnabled } from "../../config/commands.js";
import type { OpenClawConfig } from "../../config/config.js";
import { logVerbose } from "../../globals.js";
import { clampInt } from "../../utils.js";
@@ -186,7 +187,7 @@ export async function handleBashChatCommand(params: {
failures: Array<{ gate: string; key: string }>;
};
}): Promise<ReplyPayload> {
if (params.cfg.commands?.bash !== true) {
if (!isCommandFlagEnabled(params.cfg, "bash")) {
return {
text: "⚠️ bash is disabled. Set commands.bash=true to enable. Docs: https://docs.openclaw.ai/tools/slash-commands#config",
};

View File

@@ -3,6 +3,7 @@ import { resolveChannelConfigWrites } from "../../channels/plugins/config-writes
import { listPairingChannels } from "../../channels/plugins/pairing.js";
import type { ChannelId } from "../../channels/plugins/types.js";
import { normalizeChannelId } from "../../channels/registry.js";
import { isCommandFlagEnabled } from "../../config/commands.js";
import type { OpenClawConfig } from "../../config/config.js";
import {
readConfigFileSnapshot,
@@ -519,7 +520,7 @@ export const handleAllowlistCommand: CommandHandler = async (params, allowTextCo
return { shouldContinue: false, reply: { text: lines.join("\n") } };
}
if (params.cfg.commands?.config !== true) {
if (!isCommandFlagEnabled(params.cfg, "config")) {
return {
shouldContinue: false,
reply: { text: "⚠️ /allowlist edits are disabled. Set commands.config=true to enable." },

View File

@@ -1,5 +1,6 @@
import { resolveChannelConfigWrites } from "../../channels/plugins/config-writes.js";
import { normalizeChannelId } from "../../channels/registry.js";
import { isCommandFlagEnabled } from "../../config/commands.js";
import {
getConfigValueAtPath,
parseConfigPath,
@@ -36,7 +37,7 @@ export const handleConfigCommand: CommandHandler = async (params, allowTextComma
);
return { shouldContinue: false };
}
if (params.cfg.commands?.config !== true) {
if (!isCommandFlagEnabled(params.cfg, "config")) {
return {
shouldContinue: false,
reply: {
@@ -190,7 +191,7 @@ export const handleDebugCommand: CommandHandler = async (params, allowTextComman
);
return { shouldContinue: false };
}
if (params.cfg.commands?.debug !== true) {
if (!isCommandFlagEnabled(params.cfg, "debug")) {
return {
shouldContinue: false,
reply: {

View File

@@ -186,6 +186,30 @@ describe("handleCommands gating", () => {
expect(result.shouldContinue).toBe(false);
expect(result.reply?.text).toContain("/debug is disabled");
});
it("does not enable gated commands from inherited command flags", async () => {
const inheritedCommands = Object.create({
bash: true,
config: true,
debug: true,
}) as Record<string, unknown>;
const cfg = {
commands: inheritedCommands as never,
channels: { whatsapp: { allowFrom: ["*"] } },
} as OpenClawConfig;
const bashResult = await handleCommands(buildParams("/bash echo hi", cfg));
expect(bashResult.shouldContinue).toBe(false);
expect(bashResult.reply?.text).toContain("bash is disabled");
const configResult = await handleCommands(buildParams("/config show", cfg));
expect(configResult.shouldContinue).toBe(false);
expect(configResult.reply?.text).toContain("/config is disabled");
const debugResult = await handleCommands(buildParams("/debug show", cfg));
expect(debugResult.shouldContinue).toBe(false);
expect(debugResult.reply?.text).toContain("/debug is disabled");
});
});
describe("/approve command", () => {