mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 17:44:33 +00:00
fix(security): unify root-bound write hardening
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import crypto from "node:crypto";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { writeFileFromPathWithinRoot } from "../infra/fs-safe.js";
|
||||
import { sanitizeUntrustedFileName } from "./safe-filename.js";
|
||||
|
||||
function buildSiblingTempPath(targetPath: string): string {
|
||||
@@ -10,15 +11,31 @@ function buildSiblingTempPath(targetPath: string): string {
|
||||
}
|
||||
|
||||
export async function writeViaSiblingTempPath(params: {
|
||||
rootDir: string;
|
||||
targetPath: string;
|
||||
writeTemp: (tempPath: string) => Promise<void>;
|
||||
}): Promise<void> {
|
||||
const rootDir = path.resolve(params.rootDir);
|
||||
const targetPath = path.resolve(params.targetPath);
|
||||
const relativeTargetPath = path.relative(rootDir, targetPath);
|
||||
if (
|
||||
!relativeTargetPath ||
|
||||
relativeTargetPath === ".." ||
|
||||
relativeTargetPath.startsWith(`..${path.sep}`) ||
|
||||
path.isAbsolute(relativeTargetPath)
|
||||
) {
|
||||
throw new Error("Target path is outside the allowed root");
|
||||
}
|
||||
const tempPath = buildSiblingTempPath(targetPath);
|
||||
let renameSucceeded = false;
|
||||
try {
|
||||
await params.writeTemp(tempPath);
|
||||
await fs.rename(tempPath, targetPath);
|
||||
await writeFileFromPathWithinRoot({
|
||||
rootDir,
|
||||
relativePath: relativeTargetPath,
|
||||
sourcePath: tempPath,
|
||||
mkdir: false,
|
||||
});
|
||||
renameSucceeded = true;
|
||||
} finally {
|
||||
if (!renameSucceeded) {
|
||||
|
||||
@@ -4,7 +4,11 @@ import path from "node:path";
|
||||
import type { Page } from "playwright-core";
|
||||
import { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js";
|
||||
import { writeViaSiblingTempPath } from "./output-atomic.js";
|
||||
import { DEFAULT_UPLOAD_DIR, resolveStrictExistingPathsWithinRoot } from "./paths.js";
|
||||
import {
|
||||
DEFAULT_DOWNLOAD_DIR,
|
||||
DEFAULT_UPLOAD_DIR,
|
||||
resolveStrictExistingPathsWithinRoot,
|
||||
} from "./paths.js";
|
||||
import {
|
||||
ensurePageState,
|
||||
getPageForTargetId,
|
||||
@@ -92,6 +96,7 @@ async function saveDownloadPayload(download: DownloadPayload, outPath: string) {
|
||||
await download.saveAs?.(resolvedOutPath);
|
||||
} else {
|
||||
await writeViaSiblingTempPath({
|
||||
rootDir: DEFAULT_DOWNLOAD_DIR,
|
||||
targetPath: resolvedOutPath,
|
||||
writeTemp: async (tempPath) => {
|
||||
await download.saveAs?.(tempPath);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { writeViaSiblingTempPath } from "./output-atomic.js";
|
||||
import { DEFAULT_TRACE_DIR } from "./paths.js";
|
||||
import { ensureContextState, getPageForTargetId } from "./pw-session.js";
|
||||
|
||||
export async function traceStartViaPlaywright(opts: {
|
||||
@@ -34,6 +35,7 @@ export async function traceStopViaPlaywright(opts: {
|
||||
throw new Error("No active trace. Start a trace before stopping it.");
|
||||
}
|
||||
await writeViaSiblingTempPath({
|
||||
rootDir: DEFAULT_TRACE_DIR,
|
||||
targetPath: opts.path,
|
||||
writeTemp: async (tempPath) => {
|
||||
await context.tracing.stop({ path: tempPath });
|
||||
|
||||
Reference in New Issue
Block a user