mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-12 12:41:10 +00:00
fix: harden sandbox writes and centralize atomic file writes
This commit is contained in:
@@ -119,15 +119,23 @@ class SandboxFsBridgeImpl implements SandboxFsBridge {
|
||||
const buffer = Buffer.isBuffer(params.data)
|
||||
? params.data
|
||||
: Buffer.from(params.data, params.encoding ?? "utf8");
|
||||
const script =
|
||||
params.mkdir === false
|
||||
? 'set -eu; cat >"$1"'
|
||||
: 'set -eu; dir=$(dirname -- "$1"); if [ "$dir" != "." ]; then mkdir -p -- "$dir"; fi; cat >"$1"';
|
||||
await this.runCommand(script, {
|
||||
args: [target.containerPath],
|
||||
stdin: buffer,
|
||||
const tempPath = await this.writeFileToTempPath({
|
||||
targetContainerPath: target.containerPath,
|
||||
mkdir: params.mkdir !== false,
|
||||
data: buffer,
|
||||
signal: params.signal,
|
||||
});
|
||||
|
||||
try {
|
||||
await this.assertPathSafety(target, { action: "write files", requireWritable: true });
|
||||
await this.runCommand('set -eu; mv -f -- "$1" "$2"', {
|
||||
args: [tempPath, target.containerPath],
|
||||
signal: params.signal,
|
||||
});
|
||||
} catch (error) {
|
||||
await this.cleanupTempPath(tempPath, params.signal);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async mkdirp(params: { filePath: string; cwd?: string; signal?: AbortSignal }): Promise<void> {
|
||||
@@ -351,6 +359,58 @@ class SandboxFsBridgeImpl implements SandboxFsBridge {
|
||||
return normalizeContainerPath(canonical);
|
||||
}
|
||||
|
||||
private async writeFileToTempPath(params: {
|
||||
targetContainerPath: string;
|
||||
mkdir: boolean;
|
||||
data: Buffer;
|
||||
signal?: AbortSignal;
|
||||
}): Promise<string> {
|
||||
const script = params.mkdir
|
||||
? [
|
||||
"set -eu",
|
||||
'target="$1"',
|
||||
'dir=$(dirname -- "$target")',
|
||||
'if [ "$dir" != "." ]; then mkdir -p -- "$dir"; fi',
|
||||
'base=$(basename -- "$target")',
|
||||
'tmp=$(mktemp "$dir/.openclaw-write-$base.XXXXXX")',
|
||||
'cat >"$tmp"',
|
||||
'printf "%s\\n" "$tmp"',
|
||||
].join("\n")
|
||||
: [
|
||||
"set -eu",
|
||||
'target="$1"',
|
||||
'dir=$(dirname -- "$target")',
|
||||
'base=$(basename -- "$target")',
|
||||
'tmp=$(mktemp "$dir/.openclaw-write-$base.XXXXXX")',
|
||||
'cat >"$tmp"',
|
||||
'printf "%s\\n" "$tmp"',
|
||||
].join("\n");
|
||||
const result = await this.runCommand(script, {
|
||||
args: [params.targetContainerPath],
|
||||
stdin: params.data,
|
||||
signal: params.signal,
|
||||
});
|
||||
const tempPath = result.stdout.toString("utf8").trim().split(/\r?\n/).at(-1)?.trim();
|
||||
if (!tempPath || !tempPath.startsWith("/")) {
|
||||
throw new Error(
|
||||
`Failed to create temporary sandbox write path for ${params.targetContainerPath}`,
|
||||
);
|
||||
}
|
||||
return normalizeContainerPath(tempPath);
|
||||
}
|
||||
|
||||
private async cleanupTempPath(tempPath: string, signal?: AbortSignal): Promise<void> {
|
||||
try {
|
||||
await this.runCommand('set -eu; rm -f -- "$1"', {
|
||||
args: [tempPath],
|
||||
signal,
|
||||
allowFailure: true,
|
||||
});
|
||||
} catch {
|
||||
// Best-effort cleanup only.
|
||||
}
|
||||
}
|
||||
|
||||
private ensureWriteAccess(target: SandboxResolvedFsPath, action: string) {
|
||||
if (!allowsWrites(this.sandbox.workspaceAccess) || !target.writable) {
|
||||
throw new Error(`Sandbox path is read-only; cannot ${action}: ${target.containerPath}`);
|
||||
|
||||
Reference in New Issue
Block a user