security(message-tool): validate filePath/path against sandbox root#6398
Conversation
| // Validate file paths against sandbox root to prevent host file access. | ||
| const sandboxRoot = options?.sandboxRoot; | ||
| if (sandboxRoot) { | ||
| for (const key of ["filePath", "path"] as const) { | ||
| const raw = readStringParam(params, key, { trim: false }); | ||
| if (raw) { | ||
| await assertSandboxPath({ filePath: raw, cwd: sandboxRoot, root: sandboxRoot }); | ||
| } |
There was a problem hiding this comment.
[P0] sandboxRoot is used without trimming/normalizing, unlike image-tool.ts, so a value like ' ' will enable the sandbox branch but effectively set cwd/root to a non-sandbox path (e.g. current working directory), which can cause false rejections or unintended behavior depending on how assertSandboxPath resolves. Consider const sandboxRoot = options?.sandboxRoot?.trim(); and only validating when it's non-empty.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/tools/message-tool.ts
Line: 367:374
Comment:
[P0] `sandboxRoot` is used without trimming/normalizing, unlike `image-tool.ts`, so a value like `' '` will enable the sandbox branch but effectively set `cwd/root` to a non-sandbox path (e.g. current working directory), which can cause false rejections or unintended behavior depending on how `assertSandboxPath` resolves. Consider `const sandboxRoot = options?.sandboxRoot?.trim();` and only validating when it's non-empty.
How can I resolve this? If you propose a fix, please make it concise.| // Validate file paths against sandbox root to prevent host file access. | ||
| const sandboxRoot = options?.sandboxRoot; | ||
| if (sandboxRoot) { | ||
| for (const key of ["filePath", "path"] as const) { | ||
| const raw = readStringParam(params, key, { trim: false }); | ||
| if (raw) { | ||
| await assertSandboxPath({ filePath: raw, cwd: sandboxRoot, root: sandboxRoot }); | ||
| } | ||
| } |
There was a problem hiding this comment.
[P1] Validating both filePath and path unconditionally for all actions may break non-file uses of path (e.g. channels/actions that interpret path as something other than a local filesystem path). If path is only meant for attachments on send, it’s safer to scope this validation to action === "send" (and/or only when media/attachments are being used).
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/tools/message-tool.ts
Line: 367:375
Comment:
[P1] Validating both `filePath` and `path` unconditionally for all actions may break non-file uses of `path` (e.g. channels/actions that interpret `path` as something other than a local filesystem path). If `path` is only meant for attachments on `send`, it’s safer to scope this validation to `action === "send"` (and/or only when `media`/attachments are being used).
How can I resolve this? If you propose a fix, please make it concise.| it("allows filePath inside sandbox root", async () => { | ||
| mocks.runMessageAction.mockClear(); | ||
| mocks.runMessageAction.mockResolvedValue({ | ||
| kind: "send", | ||
| action: "send", | ||
| channel: "telegram", | ||
| to: "telegram:123", | ||
| handledBy: "plugin", | ||
| payload: {}, | ||
| dryRun: true, | ||
| } satisfies MessageActionRunResult); | ||
|
|
||
| const sandboxDir = await fs.mkdtemp(path.join(os.tmpdir(), "msg-sandbox-")); | ||
| try { | ||
| const tool = createMessageTool({ | ||
| config: {} as never, | ||
| sandboxRoot: sandboxDir, | ||
| }); | ||
|
|
||
| await tool.execute("1", { | ||
| action: "send", | ||
| target: "telegram:123", | ||
| filePath: "./data/file.txt", | ||
| message: "", | ||
| }); | ||
|
|
||
| expect(mocks.runMessageAction).toHaveBeenCalledTimes(1); |
There was a problem hiding this comment.
[P2] allows filePath inside sandbox root only asserts that runMessageAction was called, but doesn’t verify the validated path behavior. This can pass even if the validation is accidentally skipped. Consider asserting the call still receives the original relative filePath and (optionally) that an escaping filePath rejects in the same test suite.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/tools/message-tool.test.ts
Line: 211:237
Comment:
[P2] `allows filePath inside sandbox root` only asserts that `runMessageAction` was called, but doesn’t verify the validated path behavior. This can pass even if the validation is accidentally skipped. Consider asserting the call still receives the original relative `filePath` and (optionally) that an escaping `filePath` rejects in the same test suite.
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.|
Landed via temp rebase onto main (squash merge).
Thanks @leszekszpunar! |
…penclaw#6398) * security(message-tool): validate filePath/path against sandbox root * style: translate Polish comments to English for consistency
…penclaw#6398) * security(message-tool): validate filePath/path against sandbox root * style: translate Polish comments to English for consistency
…penclaw#6398) * security(message-tool): validate filePath/path against sandbox root * style: translate Polish comments to English for consistency
…penclaw#6398) * security(message-tool): validate filePath/path against sandbox root * style: translate Polish comments to English for consistency
…penclaw#6398) * security(message-tool): validate filePath/path against sandbox root * style: translate Polish comments to English for consistency
…penclaw#6398) * security(message-tool): validate filePath/path against sandbox root * style: translate Polish comments to English for consistency
…penclaw#6398) * security(message-tool): validate filePath/path against sandbox root * style: translate Polish comments to English for consistency
…penclaw#6398) * security(message-tool): validate filePath/path against sandbox root * style: translate Polish comments to English for consistency (cherry picked from commit 9b6fffd) # Conflicts: # src/agents/tools/message-tool.ts
…penclaw#6398) * security(message-tool): validate filePath/path against sandbox root * style: translate Polish comments to English for consistency
…penclaw#6398) * security(message-tool): validate filePath/path against sandbox root * style: translate Polish comments to English for consistency
Summary
Fixes #3805 (CRITICAL sandbox escape via message tool
filePath/pathparams).The
messagetool accepted arbitrary file paths (filePath,path) without validating them against the sandbox root directory. A sandboxed agent could read or exfiltrate host files (e.g./etc/passwd,~/.ssh/id_rsa) by passing absolute or traversal paths through the message tool's send action.Changes
src/agents/tools/message-tool.ts: AddedsandboxRootoption toMessageToolOptions. When set, bothfilePathandpathparams are validated viaassertSandboxPath()beforerunMessageAction()is called. This mirrors the pattern already used inimage-tool.tsandread/write/edittools.src/agents/openclaw-tools.ts: PasssandboxRootfrom tool creation options tocreateMessageTool().src/agents/tools/message-tool.test.ts: Added 4 tests covering sandbox path validation (reject absolute escape, reject traversal, allow valid relative path, skip validation when no sandbox).Security Impact
Without this fix, any sandboxed agent session could bypass the sandbox boundary via the message tool to access arbitrary host filesystem paths. This is a critical privilege escalation vector.
AI Disclosure
This PR was developed with AI assistance (Claude Code). The code was reviewed, tested, and verified by the author. All tests pass, linting is clean, and the fix follows existing patterns in the codebase (
image-tool.ts,read/writetools).Test Plan
pnpm vitest run src/agents/tools/message-tool.test.ts(8/8 pass)npx oxlint --type-aware --tsconfig tsconfig.oxlint.json(0 errors)npx oxfmt --check(all files formatted)main(unrelatedpi-coding-agenttype issues)Greptile Overview
Greptile Summary
This PR threads a
sandboxRootoption into themessageagent tool and uses the existingassertSandboxPath()helper to validatefilePath/pathparameters before dispatching torunMessageAction(), closing a sandbox escape vector where a sandboxed agent could reference arbitrary host paths.createOpenClawTools()now forwardssandboxRootintocreateMessageTool(), and new Vitest cases exercise absolute-path and traversal rejection, as well as the “no sandboxRoot” passthrough.The approach aligns with other sandbox-aware tools (e.g.
image-tool.tsand the read/write/edit tools) by enforcing a single sandbox boundary at the tool surface before any plugin/channel code sees the path.Confidence Score: 4/5
assertSandboxPath()sandbox boundary check, and tests cover key escape inputs. Remaining uncertainty is around howpathis interpreted across different message actions/channels and whether an untrimmedsandboxRootcould lead to unexpected behavior in misconfigured callers.