mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-26 06:01:13 +00:00
fix(sandbox): bound sandbox seed bootstrap file reads (#110017)
* fix(sandbox): bound sandbox seed bootstrap file reads ensureSandboxWorkspace copies each seed bootstrap file (AGENTS.md / SOUL.md / TOOLS.md / ...) into a fresh sandbox workspace with an unbounded readFileSync after the root-boundary open. The seed path comes from user config (seedFrom), so a misconfigured or oversized seed file forced an unbounded allocation at sandbox-creation time. Use readFileDescriptorBoundedSync with a 2 MiB limit (matching the workspace bootstrap file limit) and skip the oversized seed file with a warning instead of reading it all. * fix(sandbox): bound workspace seed reads Co-authored-by: 琚耀辉0668001366 <ju.yaohui@xydigit.com> * test(agents): cover async bootstrap read retries * test(agents): align bootstrap read mocks with main --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
@@ -4,7 +4,8 @@ import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { DEFAULT_AGENTS_FILENAME } from "../workspace.js";
|
||||
import { MAX_WORKSPACE_BOOTSTRAP_FILE_BYTES } from "../workspace-bootstrap-read.js";
|
||||
import { DEFAULT_AGENTS_FILENAME, DEFAULT_TOOLS_FILENAME } from "../workspace.js";
|
||||
import { ensureSandboxWorkspace } from "./workspace.js";
|
||||
|
||||
const tempRoots: string[] = [];
|
||||
@@ -77,4 +78,42 @@ describe("ensureSandboxWorkspace", () => {
|
||||
"no such file",
|
||||
);
|
||||
});
|
||||
|
||||
it("skips an oversized seed file but still seeds the others", async () => {
|
||||
// An unbounded read would copy the oversized file through; the bound skips it.
|
||||
const root = await makeTempRoot();
|
||||
const seed = path.join(root, "seed");
|
||||
const sandbox = path.join(root, "sandbox");
|
||||
await fs.mkdir(seed, { recursive: true });
|
||||
await fs.writeFile(
|
||||
path.join(seed, DEFAULT_AGENTS_FILENAME),
|
||||
`## Startup\n\n` + "x".repeat(MAX_WORKSPACE_BOOTSTRAP_FILE_BYTES),
|
||||
"utf-8",
|
||||
);
|
||||
await fs.writeFile(path.join(seed, DEFAULT_TOOLS_FILENAME), "seeded-tools", "utf-8");
|
||||
|
||||
await ensureSandboxWorkspace(sandbox, seed, true);
|
||||
|
||||
await expect(fs.readFile(path.join(sandbox, DEFAULT_AGENTS_FILENAME), "utf-8")).rejects.toThrow(
|
||||
"no such file",
|
||||
);
|
||||
await expect(fs.readFile(path.join(sandbox, DEFAULT_TOOLS_FILENAME), "utf-8")).resolves.toBe(
|
||||
"seeded-tools",
|
||||
);
|
||||
});
|
||||
|
||||
it("seeds a bootstrap file at the byte read limit", async () => {
|
||||
const root = await makeTempRoot();
|
||||
const seed = path.join(root, "seed");
|
||||
const sandbox = path.join(root, "sandbox");
|
||||
await fs.mkdir(seed, { recursive: true });
|
||||
const content = "## Startup\n\nDo startup things.\n";
|
||||
const padding = "x".repeat(MAX_WORKSPACE_BOOTSTRAP_FILE_BYTES - content.length);
|
||||
await fs.writeFile(path.join(seed, DEFAULT_AGENTS_FILENAME), content + padding, "utf-8");
|
||||
|
||||
await ensureSandboxWorkspace(sandbox, seed, true);
|
||||
|
||||
const seeded = await fs.readFile(path.join(sandbox, DEFAULT_AGENTS_FILENAME), "utf-8");
|
||||
expect(seeded).toContain("Do startup things");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,7 +8,12 @@ import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import type { OptionalBootstrapFileName } from "../../config/types.agent-defaults.js";
|
||||
import { openRootFile } from "../../infra/boundary-file-read.js";
|
||||
import { createSubsystemLogger } from "../../logging/subsystem.js";
|
||||
import { resolveUserPath } from "../../utils.js";
|
||||
import {
|
||||
MAX_WORKSPACE_BOOTSTRAP_FILE_BYTES,
|
||||
readWorkspaceBootstrapFile,
|
||||
} from "../workspace-bootstrap-read.js";
|
||||
import {
|
||||
DEFAULT_AGENTS_FILENAME,
|
||||
DEFAULT_BOOTSTRAP_FILENAME,
|
||||
@@ -20,6 +25,8 @@ import {
|
||||
ensureAgentWorkspace,
|
||||
} from "../workspace.js";
|
||||
|
||||
const log = createSubsystemLogger("sandbox-workspace");
|
||||
|
||||
export async function ensureSandboxWorkspace(
|
||||
workspaceDir: string,
|
||||
seedFrom?: string,
|
||||
@@ -54,8 +61,15 @@ export async function ensureSandboxWorkspace(
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const content = syncFs.readFileSync(opened.fd, "utf-8");
|
||||
const content = await readWorkspaceBootstrapFile(opened.fd);
|
||||
await fs.writeFile(dest, content, { encoding: "utf-8", flag: "wx" });
|
||||
} catch (err) {
|
||||
if (err instanceof RangeError) {
|
||||
log.warn(
|
||||
`Ignoring oversized sandbox seed file ${src}: file exceeds the ${MAX_WORKSPACE_BOOTSTRAP_FILE_BYTES}-byte limit`,
|
||||
);
|
||||
}
|
||||
// ignore missing or oversized seed file
|
||||
} finally {
|
||||
syncFs.closeSync(opened.fd);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user