Skip to content

Commit b67e600

Browse files
committed
fix(security): restrict default safe-bin trusted dirs
1 parent 2d159e5 commit b67e600

6 files changed

Lines changed: 32 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Docs: https://docs.openclaw.ai
1616
- 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.
1717
- 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.
1818
- 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.
19+
- 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.
1920
- 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.
2021
- 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.
2122
- 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.

docs/tools/exec-approvals.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,10 @@ and no `$VARS` expansion) for stdin-only segments, so patterns like `*` or `$HOM
165165
used to smuggle file reads.
166166
Safe bins must also resolve from trusted binary directories (system defaults plus optional
167167
`tools.exec.safeBinTrustedDirs`). `PATH` entries are never auto-trusted.
168+
Default trusted safe-bin directories are intentionally minimal: `/bin`, `/usr/bin`.
169+
If your safe-bin executable lives in package-manager/user paths (for example
170+
`/opt/homebrew/bin`, `/usr/local/bin`, `/opt/local/bin`, `/snap/bin`), add them explicitly
171+
to `tools.exec.safeBinTrustedDirs`.
168172
Shell chaining and redirections are not auto-allowed in allowlist mode.
169173

170174
Shell chaining (`&&`, `||`, `;`) is allowed when every top-level segment satisfies the allowlist

docs/tools/exec.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ Notes:
5555
- `tools.exec.node` (default: unset)
5656
- `tools.exec.pathPrepend`: list of directories to prepend to `PATH` for exec runs (gateway + sandbox only).
5757
- `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).
58-
- `tools.exec.safeBinTrustedDirs`: additional explicit directories trusted for `safeBins` path checks. `PATH` entries are never auto-trusted.
58+
- `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`.
5959
- `tools.exec.safeBinProfiles`: optional custom argv policy per safe bin (`minPositional`, `maxPositional`, `allowedValueFlags`, `deniedFlags`).
6060

6161
Example:

src/infra/exec-safe-bin-runtime-policy.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,18 @@ describe("exec safe-bin runtime policy", () => {
8989
expect(policy.trustedSafeBinDirs.has(path.resolve(customDir))).toBe(true);
9090
expect(policy.trustedSafeBinDirs.has(path.resolve(agentDir))).toBe(true);
9191
});
92+
93+
it("does not trust package-manager bin dirs unless explicitly configured", () => {
94+
const defaultPolicy = resolveExecSafeBinRuntimePolicy({});
95+
expect(defaultPolicy.trustedSafeBinDirs.has(path.resolve("/opt/homebrew/bin"))).toBe(false);
96+
expect(defaultPolicy.trustedSafeBinDirs.has(path.resolve("/usr/local/bin"))).toBe(false);
97+
98+
const optedIn = resolveExecSafeBinRuntimePolicy({
99+
global: {
100+
safeBinTrustedDirs: ["/opt/homebrew/bin", "/usr/local/bin"],
101+
},
102+
});
103+
expect(optedIn.trustedSafeBinDirs.has(path.resolve("/opt/homebrew/bin"))).toBe(true);
104+
expect(optedIn.trustedSafeBinDirs.has(path.resolve("/usr/local/bin"))).toBe(true);
105+
});
92106
});

src/infra/exec-safe-bin-trust.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ import {
88
} from "./exec-safe-bin-trust.js";
99

1010
describe("exec safe bin trust", () => {
11+
it("keeps default trusted dirs limited to immutable system paths", () => {
12+
const dirs = getTrustedSafeBinDirs({ refresh: true });
13+
14+
expect(dirs.has(path.resolve("/bin"))).toBe(true);
15+
expect(dirs.has(path.resolve("/usr/bin"))).toBe(true);
16+
expect(dirs.has(path.resolve("/usr/local/bin"))).toBe(false);
17+
expect(dirs.has(path.resolve("/opt/homebrew/bin"))).toBe(false);
18+
});
19+
1120
it("builds trusted dirs from defaults and explicit extra dirs", () => {
1221
const dirs = buildTrustedSafeBinDirs({
1322
baseDirs: ["/usr/bin"],

src/infra/exec-safe-bin-trust.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
11
import path from "node:path";
22

3-
const DEFAULT_SAFE_BIN_TRUSTED_DIRS = [
4-
"/bin",
5-
"/usr/bin",
6-
"/usr/local/bin",
7-
"/opt/homebrew/bin",
8-
"/opt/local/bin",
9-
"/snap/bin",
10-
"/run/current-system/sw/bin",
11-
];
3+
// Keep defaults to OS-managed immutable bins only.
4+
// User/package-manager bins must be opted in via tools.exec.safeBinTrustedDirs.
5+
const DEFAULT_SAFE_BIN_TRUSTED_DIRS = ["/bin", "/usr/bin"];
126

137
type TrustedSafeBinDirsParams = {
148
baseDirs?: readonly string[];

0 commit comments

Comments
 (0)