diff --git a/CHANGELOG.md b/CHANGELOG.md index cf40d8a20aa..fa6315648c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Docs: https://docs.openclaw.ai - Security/Workspace FS: normalize `@`-prefixed paths before workspace-boundary checks (including workspace-only read/write/edit and sandbox mount path guards), preventing absolute-path escape attempts from bypassing guard validation. This ships in the next npm release. Thanks @tdjackey for reporting. - Security/Native images: enforce `tools.fs.workspaceOnly` for native prompt image auto-load (including history refs), preventing out-of-workspace sandbox mounts from being implicitly ingested as vision input. This ships in the next npm release. Thanks @tdjackey for reporting. - Security/Exec approvals: bind `system.run` command display/approval text to full argv when shell-wrapper inline payloads carry positional argv values, and reject payload-only `rawCommand` mismatches for those wrapper-carrier forms, preventing hidden command execution under misleading approval text. This ships in the next npm release. Thanks @tdjackey for reporting. +- Security/Exec: limit default safe-bin trusted directories to immutable system paths (`/bin`, `/usr/bin`) and require explicit opt-in (`tools.exec.safeBinTrustedDirs`) for package-manager/user bin paths (for example Homebrew), preventing writable-dir binary shadowing from auto-satisfying safe-bin allowlist checks. This ships in the next npm release. Thanks @tdjackey for reporting. - Telegram/Media fetch: prioritize IPv4 before IPv6 in SSRF pinned DNS address ordering so media downloads still work on hosts with broken IPv6 routing. (#24295, #23975) Thanks @Glucksberg. - Telegram/Replies: when markdown formatting renders to empty HTML (for example syntax-only chunks in threaded replies), retry delivery with plain text, and fail loud when both formatted and plain payloads are empty to avoid false delivered states. (#25096, #25091) Thanks @Glucksberg. - Sessions/Tool-result guard: avoid generating synthetic `toolResult` entries for assistant turns that ended with `stopReason: "aborted"` or `"error"`, preventing orphaned tool-use IDs from triggering downstream API validation errors. (#25429) Thanks @mikaeldiakhate-cell. diff --git a/docs/tools/exec-approvals.md b/docs/tools/exec-approvals.md index f155fbbd790..619f5cdb38e 100644 --- a/docs/tools/exec-approvals.md +++ b/docs/tools/exec-approvals.md @@ -165,6 +165,10 @@ and no `$VARS` expansion) for stdin-only segments, so patterns like `*` or `$HOM used to smuggle file reads. Safe bins must also resolve from trusted binary directories (system defaults plus optional `tools.exec.safeBinTrustedDirs`). `PATH` entries are never auto-trusted. +Default trusted safe-bin directories are intentionally minimal: `/bin`, `/usr/bin`. +If your safe-bin executable lives in package-manager/user paths (for example +`/opt/homebrew/bin`, `/usr/local/bin`, `/opt/local/bin`, `/snap/bin`), add them explicitly +to `tools.exec.safeBinTrustedDirs`. Shell chaining and redirections are not auto-allowed in allowlist mode. Shell chaining (`&&`, `||`, `;`) is allowed when every top-level segment satisfies the allowlist diff --git a/docs/tools/exec.md b/docs/tools/exec.md index 1dc5cc4fc1d..a52af45fdcb 100644 --- a/docs/tools/exec.md +++ b/docs/tools/exec.md @@ -55,7 +55,7 @@ Notes: - `tools.exec.node` (default: unset) - `tools.exec.pathPrepend`: list of directories to prepend to `PATH` for exec runs (gateway + sandbox only). - `tools.exec.safeBins`: stdin-only safe binaries that can run without explicit allowlist entries. For behavior details, see [Safe bins](/tools/exec-approvals#safe-bins-stdin-only). -- `tools.exec.safeBinTrustedDirs`: additional explicit directories trusted for `safeBins` path checks. `PATH` entries are never auto-trusted. +- `tools.exec.safeBinTrustedDirs`: additional explicit directories trusted for `safeBins` path checks. `PATH` entries are never auto-trusted. Built-in defaults are `/bin` and `/usr/bin`. - `tools.exec.safeBinProfiles`: optional custom argv policy per safe bin (`minPositional`, `maxPositional`, `allowedValueFlags`, `deniedFlags`). Example: diff --git a/src/infra/exec-safe-bin-runtime-policy.test.ts b/src/infra/exec-safe-bin-runtime-policy.test.ts index e9ee3230405..94cc868c5b2 100644 --- a/src/infra/exec-safe-bin-runtime-policy.test.ts +++ b/src/infra/exec-safe-bin-runtime-policy.test.ts @@ -89,4 +89,18 @@ describe("exec safe-bin runtime policy", () => { expect(policy.trustedSafeBinDirs.has(path.resolve(customDir))).toBe(true); expect(policy.trustedSafeBinDirs.has(path.resolve(agentDir))).toBe(true); }); + + it("does not trust package-manager bin dirs unless explicitly configured", () => { + const defaultPolicy = resolveExecSafeBinRuntimePolicy({}); + expect(defaultPolicy.trustedSafeBinDirs.has(path.resolve("/opt/homebrew/bin"))).toBe(false); + expect(defaultPolicy.trustedSafeBinDirs.has(path.resolve("/usr/local/bin"))).toBe(false); + + const optedIn = resolveExecSafeBinRuntimePolicy({ + global: { + safeBinTrustedDirs: ["/opt/homebrew/bin", "/usr/local/bin"], + }, + }); + expect(optedIn.trustedSafeBinDirs.has(path.resolve("/opt/homebrew/bin"))).toBe(true); + expect(optedIn.trustedSafeBinDirs.has(path.resolve("/usr/local/bin"))).toBe(true); + }); }); diff --git a/src/infra/exec-safe-bin-trust.test.ts b/src/infra/exec-safe-bin-trust.test.ts index f653b13ca7e..eccd6cce986 100644 --- a/src/infra/exec-safe-bin-trust.test.ts +++ b/src/infra/exec-safe-bin-trust.test.ts @@ -8,6 +8,15 @@ import { } from "./exec-safe-bin-trust.js"; describe("exec safe bin trust", () => { + it("keeps default trusted dirs limited to immutable system paths", () => { + const dirs = getTrustedSafeBinDirs({ refresh: true }); + + expect(dirs.has(path.resolve("/bin"))).toBe(true); + expect(dirs.has(path.resolve("/usr/bin"))).toBe(true); + expect(dirs.has(path.resolve("/usr/local/bin"))).toBe(false); + expect(dirs.has(path.resolve("/opt/homebrew/bin"))).toBe(false); + }); + it("builds trusted dirs from defaults and explicit extra dirs", () => { const dirs = buildTrustedSafeBinDirs({ baseDirs: ["/usr/bin"], diff --git a/src/infra/exec-safe-bin-trust.ts b/src/infra/exec-safe-bin-trust.ts index 9edfb16a449..e939ac71711 100644 --- a/src/infra/exec-safe-bin-trust.ts +++ b/src/infra/exec-safe-bin-trust.ts @@ -1,14 +1,8 @@ import path from "node:path"; -const DEFAULT_SAFE_BIN_TRUSTED_DIRS = [ - "/bin", - "/usr/bin", - "/usr/local/bin", - "/opt/homebrew/bin", - "/opt/local/bin", - "/snap/bin", - "/run/current-system/sw/bin", -]; +// Keep defaults to OS-managed immutable bins only. +// User/package-manager bins must be opted in via tools.exec.safeBinTrustedDirs. +const DEFAULT_SAFE_BIN_TRUSTED_DIRS = ["/bin", "/usr/bin"]; type TrustedSafeBinDirsParams = { baseDirs?: readonly string[];