From 42f455739f03c553bb0d7014e8152078b85d8e54 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 26 Feb 2026 00:55:25 +0100 Subject: [PATCH] fix(security): clarify denyCommands exact-match guidance --- docs/cli/security.md | 2 +- docs/gateway/security/index.md | 2 +- src/config/schema.help.ts | 2 +- src/node-host/invoke-system-run.test.ts | 25 +++++++++++++++++++++++++ src/security/audit-extra.sync.ts | 4 ++-- 5 files changed, 30 insertions(+), 5 deletions(-) diff --git a/docs/cli/security.md b/docs/cli/security.md index fe8af41ec25..cc705b31a30 100644 --- a/docs/cli/security.md +++ b/docs/cli/security.md @@ -29,7 +29,7 @@ It also emits `security.trust_model.multi_user_heuristic` when config suggests l For intentional shared-user setups, the audit guidance is to sandbox all sessions, keep filesystem access workspace-scoped, and keep personal/private identities or credentials off that runtime. It also warns when small models (`<=300B`) are used without sandboxing and with web/browser tools enabled. For webhook ingress, it warns when `hooks.defaultSessionKey` is unset, when request `sessionKey` overrides are enabled, and when overrides are enabled without `hooks.allowedSessionKeyPrefixes`. -It also warns when sandbox Docker settings are configured while sandbox mode is off, when `gateway.nodes.denyCommands` uses ineffective pattern-like/unknown entries, when `gateway.nodes.allowCommands` explicitly enables dangerous node commands, when global `tools.profile="minimal"` is overridden by agent tool profiles, when open groups expose runtime/filesystem tools without sandbox/workspace guards, and when installed extension plugin tools may be reachable under permissive tool policy. +It also warns when sandbox Docker settings are configured while sandbox mode is off, when `gateway.nodes.denyCommands` uses ineffective pattern-like/unknown entries (exact node command-name matching only, not shell-text filtering), when `gateway.nodes.allowCommands` explicitly enables dangerous node commands, when global `tools.profile="minimal"` is overridden by agent tool profiles, when open groups expose runtime/filesystem tools without sandbox/workspace guards, and when installed extension plugin tools may be reachable under permissive tool policy. It also flags `gateway.allowRealIpFallback=true` (header-spoofing risk if proxies are misconfigured) and `discovery.mdns.mode="full"` (metadata leakage via mDNS TXT records). It also warns when sandbox browser uses Docker `bridge` network without `sandbox.browser.cdpSourceRange`. It also flags dangerous sandbox Docker network modes (including `host` and `container:*` namespace joins). diff --git a/docs/gateway/security/index.md b/docs/gateway/security/index.md index 3824d1d283e..a61a81eab1e 100644 --- a/docs/gateway/security/index.md +++ b/docs/gateway/security/index.md @@ -188,7 +188,7 @@ If more than one person can DM your bot: - **Browser control exposure** (remote nodes, relay ports, remote CDP endpoints). - **Local disk hygiene** (permissions, symlinks, config includes, “synced folder” paths). - **Plugins** (extensions exist without an explicit allowlist). -- **Policy drift/misconfig** (sandbox docker settings configured but sandbox mode off; ineffective `gateway.nodes.denyCommands` patterns; dangerous `gateway.nodes.allowCommands` entries; global `tools.profile="minimal"` overridden by per-agent profiles; extension plugin tools reachable under permissive tool policy). +- **Policy drift/misconfig** (sandbox docker settings configured but sandbox mode off; ineffective `gateway.nodes.denyCommands` patterns because matching is exact command-name only (for example `system.run`) and does not inspect shell text; dangerous `gateway.nodes.allowCommands` entries; global `tools.profile="minimal"` overridden by per-agent profiles; extension plugin tools reachable under permissive tool policy). - **Runtime expectation drift** (for example `tools.exec.host="sandbox"` while sandbox mode is off, which runs directly on the gateway host). - **Model hygiene** (warn when configured models look legacy; not a hard block). diff --git a/src/config/schema.help.ts b/src/config/schema.help.ts index bf917461f56..a479ec0a853 100644 --- a/src/config/schema.help.ts +++ b/src/config/schema.help.ts @@ -330,7 +330,7 @@ export const FIELD_HELP: Record = { "gateway.nodes.allowCommands": "Extra node.invoke commands to allow beyond the gateway defaults (array of command strings). Enabling dangerous commands here is a security-sensitive override and is flagged by `openclaw security audit`.", "gateway.nodes.denyCommands": - "Commands to block even if present in node claims or default allowlist.", + "Node command names to block even if present in node claims or default allowlist (exact command-name matching only, e.g. `system.run`; does not inspect shell text inside that command).", nodeHost: "Node host controls for features exposed from this gateway node to other nodes or clients. Keep defaults unless you intentionally proxy local capabilities across your node network.", "nodeHost.browserProxy": diff --git a/src/node-host/invoke-system-run.test.ts b/src/node-host/invoke-system-run.test.ts index 2d939c7726e..d1917199067 100644 --- a/src/node-host/invoke-system-run.test.ts +++ b/src/node-host/invoke-system-run.test.ts @@ -365,6 +365,31 @@ describe("handleSystemRunInvoke mac app exec host routing", () => { ); }); + it("denies semicolon-chained shell payloads in allowlist mode without explicit approval", async () => { + const payloads = ["openclaw status; id", "openclaw status; cat /etc/passwd"]; + for (const payload of payloads) { + const command = + process.platform === "win32" + ? ["cmd.exe", "/d", "/s", "/c", payload] + : ["/bin/sh", "-lc", payload]; + const { runCommand, sendInvokeResult } = await runSystemInvoke({ + preferMacAppExecHost: false, + security: "allowlist", + ask: "on-miss", + command, + }); + expect(runCommand, payload).not.toHaveBeenCalled(); + expect(sendInvokeResult, payload).toHaveBeenCalledWith( + expect.objectContaining({ + ok: false, + error: expect.objectContaining({ + message: "SYSTEM_RUN_DENIED: approval required", + }), + }), + ); + } + }); + it("denies nested env shell payloads when wrapper depth is exceeded", async () => { if (process.platform === "win32") { return; diff --git a/src/security/audit-extra.sync.ts b/src/security/audit-extra.sync.ts index daa60aed73f..a3f81d40870 100644 --- a/src/security/audit-extra.sync.ts +++ b/src/security/audit-extra.sync.ts @@ -955,11 +955,11 @@ export function collectNodeDenyCommandPatternFindings(cfg: OpenClawConfig): Secu severity: "warn", title: "Some gateway.nodes.denyCommands entries are ineffective", detail: - "gateway.nodes.denyCommands uses exact command-name matching only.\n" + + "gateway.nodes.denyCommands uses exact node command-name matching only (for example `system.run`), not shell-text filtering inside a command payload.\n" + detailParts.map((entry) => `- ${entry}`).join("\n"), remediation: `Use exact command names (for example: ${examples.join(", ")}). ` + - "If you need broader restrictions, remove risky commands from allowCommands/default workflows.", + "If you need broader restrictions, remove risky command IDs from allowCommands/default workflows and tighten tools.exec policy.", }); return findings;