fix(tools): land #31015 from @haosenwang1018

Co-authored-by: haosenwang1018 <1293965075@qq.com>
This commit is contained in:
Peter Steinberger 2026-03-02 01:00:49 +00:00
parent ac3e1e769b
commit da80e22d89
3 changed files with 21 additions and 4 deletions

View File

@ -20,6 +20,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Tools/Edit workspace boundary errors: preserve the real `Path escapes workspace root` failure path instead of surfacing a misleading access/file-not-found error when editing outside workspace roots. Landed from contributor PR #31015 by @haosenwang1018. Thanks @haosenwang1018.
- Sandbox/mkdirp boundary checks: allow directory-safe boundary validation for existing in-boundary subdirectories, preventing false `cannot create directories` failures in sandbox write mode. (#30610) Thanks @glitch418x.
- Android/Voice screen TTS: stream assistant speech via ElevenLabs WebSocket in Talk Mode, stop cleanly on speaker mute/barge-in, and ignore stale out-of-order stream events. (#29521) Thanks @gregmousseau.
- Web UI/Cron: include configured agent model defaults/fallbacks in cron model suggestions so scheduled-job model autocomplete reflects configured models. (#29709) Thanks @Sid-Qin.

View File

@ -43,7 +43,7 @@ describe("createHostWorkspaceEditTool host access mapping", () => {
});
it.runIf(process.platform !== "win32")(
"maps outside-workspace safe-open failures to EACCES",
"silently passes access for outside-workspace paths so readFile reports the real error",
async () => {
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-edit-access-test-"));
const workspaceDir = path.join(tmpDir, "workspace");
@ -58,9 +58,13 @@ describe("createHostWorkspaceEditTool host access mapping", () => {
createHostWorkspaceEditTool(workspaceDir, { workspaceOnly: true });
expect(mocks.operations).toBeDefined();
// access must NOT throw for outside-workspace paths; the upstream
// library replaces any access error with a misleading "File not found".
// By resolving silently the subsequent readFile call surfaces the real
// "Path escapes workspace root" / "outside-workspace" error instead.
await expect(
mocks.operations!.access(path.join(workspaceDir, "escape", "secret.txt")),
).rejects.toMatchObject({ code: "EACCES" });
).resolves.toBeUndefined();
},
);
});

View File

@ -843,7 +843,17 @@ function createHostEditOperations(root: string, options?: { workspaceOnly?: bool
});
},
access: async (absolutePath: string) => {
const relative = toRelativePathInRoot(root, absolutePath);
let relative: string;
try {
relative = toRelativePathInRoot(root, absolutePath);
} catch {
// Path escapes workspace root. Don't throw here the upstream
// library replaces any `access` error with a misleading "File not
// found" message. By returning silently the subsequent `readFile`
// call will throw the same "Path escapes workspace root" error
// through a code-path that propagates the original message.
return;
}
try {
const opened = await openFileWithinRoot({
rootDir: root,
@ -855,7 +865,9 @@ function createHostEditOperations(root: string, options?: { workspaceOnly?: bool
throw createFsAccessError("ENOENT", absolutePath);
}
if (error instanceof SafeOpenError && error.code === "outside-workspace") {
throw createFsAccessError("EACCES", absolutePath);
// Don't throw here see the comment above about the upstream
// library swallowing access errors as "File not found".
return;
}
throw error;
}