openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
2,253 lines • 88.9 kB
JavaScript
import { r as getFsSafePythonConfig, t as canFallbackFromPythonError } from "./fs-safe-defaults-B7hUN42l.js";
import { i as isPathInside, m as FsSafeError, n as hasNodeErrorCode, o as isPathRelativeEscape, r as isNotFoundPathError, s as isSymlinkOpenError, t as assertNoNulPathInput } from "./path-BlG8lhgR.js";
import { t as sameFileIdentity } from "./file-identity-BKNyWMFA.js";
import { n as normalizeOptionalString } from "./string-coerce-6TL5VVOL.js";
import { c as assertAsyncDirectoryGuard, d as createNearestExistingDirectoryGuard, n as registerTempPathForExit, s as withAsyncDirectoryGuards, t as serializePathWrite, u as createAsyncDirectoryGuard } from "./write-queue-C9nceBqy.js";
import { r as resolveRootPath, t as ROOT_PATH_ALIAS_POLICIES } from "./root-path-BgCKz8X4.js";
import { t as stringifyJsonDocument } from "./json-stringify-DYDqVIo7.js";
import fs, { constants } from "node:fs";
import path from "node:path";
import fs$1 from "node:fs/promises";
import os, { tmpdir } from "node:os";
import { spawn } from "node:child_process";
import { randomUUID } from "node:crypto";
import { pipeline } from "node:stream/promises";
import { Transform } from "node:stream";
//#region node_modules/@openclaw/fs-safe/dist/home-dir.js
function normalize$1(value) {
const trimmed = normalizeOptionalString(value);
if (!trimmed) return;
if (trimmed === "undefined" || trimmed === "null") return;
return trimmed;
}
function resolveEffectiveHomeDir(env = process.env, homedir = os.homedir) {
const raw = resolveRawHomeDir(env, homedir);
return raw ? path.resolve(raw) : void 0;
}
function resolveRawHomeDir(env, homedir) {
const explicitHome = normalize$1(env.OPENCLAW_HOME);
if (!explicitHome) return resolveRawOsHomeDir(env, homedir);
if (path.normalize(explicitHome).split(path.sep)[0] !== "~") return explicitHome;
const fallbackHome = resolveRawOsHomeDir(env, homedir);
if (!fallbackHome) return;
return expandHomePrefix(explicitHome, { home: fallbackHome });
}
function resolveRawOsHomeDir(env, homedir) {
const envHome = normalize$1(env.HOME);
if (envHome) return envHome;
const userProfile = normalize$1(env.USERPROFILE);
if (userProfile) return userProfile;
return normalizeSafe(homedir);
}
function normalizeSafe(homedir) {
try {
return normalize$1(homedir());
} catch {
return;
}
}
function resolveRequiredHomeDir(env = process.env, homedir = os.homedir) {
return resolveEffectiveHomeDir(env, homedir) ?? path.resolve(process.cwd());
}
function expandHomePrefix(input, opts) {
const segments = path.normalize(input).split(path.sep);
if (segments[0] !== "~") return input;
const home = normalize$1(opts?.home) ?? resolveEffectiveHomeDir(opts?.env ?? process.env, opts?.homedir ?? os.homedir);
if (!home) return input;
return path.join(home, ...segments.slice(1));
}
function resolveHomeRelativePath(input, opts) {
if (!input) return input;
if (path.normalize(input).split(path.sep)[0] !== "~") return path.resolve(input);
const expanded = expandHomePrefix(input, {
home: resolveRequiredHomeDir(opts?.env ?? process.env, opts?.homedir ?? os.homedir),
env: opts?.env,
homedir: opts?.homedir
});
return path.resolve(expanded);
}
function resolveUserPath(input, optsOrEnv, homedir) {
return resolveHomeRelativePath(input, optsOrEnv && ("env" in optsOrEnv || "homedir" in optsOrEnv) ? optsOrEnv : {
env: optsOrEnv,
homedir
});
}
//#endregion
//#region node_modules/@openclaw/fs-safe/dist/bounded-read-stream.js
function createMaxBytesTransform(maxBytes) {
let bytes = 0;
return new Transform({ transform(chunk, _encoding, callback) {
const buffer = chunk instanceof Buffer ? chunk : Buffer.from(chunk);
bytes += buffer.byteLength;
if (bytes > maxBytes) {
callback(new FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`));
return;
}
callback(null, buffer);
} });
}
function createBoundedReadStream(opened, maxBytes) {
const stream = opened.handle.createReadStream();
return maxBytes === void 0 ? stream : stream.pipe(createMaxBytesTransform(maxBytes));
}
//#endregion
//#region node_modules/@openclaw/fs-safe/dist/guarded-mkdir.js
function isSameOrChildPath(candidate, parent) {
const parentPrefix = parent.endsWith(path.sep) ? parent : `${parent}${path.sep}`;
return candidate === parent || candidate.startsWith(parentPrefix);
}
async function mkdirPathComponentsWithGuards(params) {
const root = path.resolve(params.rootReal);
const rootCanonical = path.resolve(await fs$1.realpath(root));
const target = path.resolve(params.targetPath);
const relative = path.relative(root, target);
if (isPathRelativeEscape(relative)) throw new FsSafeError("outside-workspace", "directory is outside workspace root");
let current = root;
for (const part of relative.split(path.sep).filter(Boolean)) {
const next = path.join(current, part);
const parentGuard = await createAsyncDirectoryGuard(current);
await params.beforeComponent?.(next);
await assertAsyncDirectoryGuard(parentGuard);
try {
await fs$1.mkdir(next);
} catch (error) {
if (!error || typeof error !== "object" || !("code" in error) || error.code !== "EEXIST") throw error;
}
const stat = await fs$1.lstat(next);
if (stat.isSymbolicLink() || !stat.isDirectory()) throw new FsSafeError("not-file", "directory component must be a directory");
if (!isSameOrChildPath(path.resolve(await fs$1.realpath(next)), rootCanonical)) throw new FsSafeError("outside-workspace", "directory escaped workspace root");
await createAsyncDirectoryGuard(next);
await assertAsyncDirectoryGuard(parentGuard);
current = next;
}
}
//#endregion
//#region node_modules/@openclaw/fs-safe/dist/deny-mutations.js
async function pathExists(filePath) {
try {
await fs$1.lstat(filePath);
return true;
} catch (err) {
if (!isNotFoundPathError(err)) throw err;
return false;
}
}
async function resolvePathViaExistingAncestor(targetPath) {
const normalized = path.resolve(targetPath);
let cursor = normalized;
const missingSuffix = [];
while (path.dirname(cursor) !== cursor && !await pathExists(cursor)) {
missingSuffix.unshift(path.basename(cursor));
cursor = path.dirname(cursor);
}
if (!await pathExists(cursor)) return normalized;
try {
const resolvedAncestor = path.resolve(await fs$1.realpath(cursor));
return missingSuffix.length === 0 ? resolvedAncestor : path.resolve(resolvedAncestor, ...missingSuffix);
} catch {
return normalized;
}
}
async function comparablePaths(rawPath) {
assertNoNulPathInput(rawPath, "path contains a NUL byte");
const resolved = path.resolve(rawPath);
return new Set([resolved, await resolvePathViaExistingAncestor(resolved)]);
}
function isSamePath(left, right) {
return isPathInside(left, right) && isPathInside(right, left);
}
function hasPolicyEntries(policy) {
return Boolean(policy?.paths?.length || policy?.prefixes?.length);
}
function policyPathEntries(entries) {
const paths = [];
for (const entry of entries ?? []) {
if (entry.length === 0) throw new FsSafeError("invalid-path", "deny mutation paths must be non-empty");
assertNoNulPathInput(entry, "deny mutation path contains a NUL byte");
if (!path.isAbsolute(entry)) throw new FsSafeError("invalid-path", "deny mutation paths must be absolute");
paths.push(entry);
}
return paths;
}
async function assertMutationNotDenied(filePath, policy, options = {}) {
if (!hasPolicyEntries(policy)) return;
const targetPaths = await comparablePaths(filePath);
for (const deniedPath of policyPathEntries(policy.paths)) {
const deniedPaths = await comparablePaths(deniedPath);
for (const target of targetPaths) for (const denied of deniedPaths) if (isSamePath(denied, target) || options.protectAncestors === true && isPathInside(target, denied)) throw new FsSafeError("denied-path", "path is denied by denyMutations policy");
}
for (const deniedPrefix of policyPathEntries(policy.prefixes)) {
const deniedPaths = await comparablePaths(deniedPrefix);
for (const target of targetPaths) for (const denied of deniedPaths) if (isPathInside(denied, target) || options.protectAncestors === true && isPathInside(target, denied)) throw new FsSafeError("denied-path", "path is denied by denyMutations policy");
}
}
function mergeDenyMutationPolicies(defaultPolicy, callPolicy) {
if (!defaultPolicy) return callPolicy;
if (!callPolicy) return defaultPolicy;
return {
paths: [...defaultPolicy.paths ?? [], ...callPolicy.paths ?? []],
prefixes: [...defaultPolicy.prefixes ?? [], ...callPolicy.prefixes ?? []]
};
}
//#endregion
//#region node_modules/@openclaw/fs-safe/dist/opened-realpath.js
async function resolveOpenedFileRealPathForHandle(handle, ioPath) {
const handleStat = await handle.stat();
const fdCandidates = process.platform === "linux" ? [`/proc/self/fd/${handle.fd}`, `/dev/fd/${handle.fd}`] : process.platform === "win32" ? [] : [`/dev/fd/${handle.fd}`];
for (const fdPath of fdCandidates) try {
const fdRealPath = await fs$1.realpath(fdPath);
if (sameFileIdentity(handleStat, await fs$1.stat(fdRealPath))) return fdRealPath;
} catch {}
try {
const ioRealPath = await fs$1.realpath(ioPath);
if (sameFileIdentity(handleStat, await fs$1.stat(ioRealPath))) return ioRealPath;
} catch (err) {
if (!isNotFoundPathError(err)) throw err;
}
const parentResolved = await resolveOpenedFileRealPathFromParent(handleStat, ioPath);
if (parentResolved) return parentResolved;
throw new FsSafeError("path-mismatch", "unable to resolve opened file path");
}
async function resolveOpenedFileRealPathFromParent(handleStat, ioPath) {
let parentReal;
try {
parentReal = await fs$1.realpath(path.dirname(ioPath));
} catch (err) {
if (isNotFoundPathError(err)) return null;
throw err;
}
let entries;
try {
entries = await fs$1.readdir(parentReal);
} catch (err) {
if (isNotFoundPathError(err)) return null;
throw err;
}
for (const entry of entries.toSorted()) {
const candidatePath = path.join(parentReal, entry);
try {
const candidateStat = await fs$1.lstat(candidatePath);
if (candidateStat.isFile() && sameFileIdentity(handleStat, candidateStat)) return await fs$1.realpath(candidatePath);
} catch (err) {
if (!isNotFoundPathError(err)) throw err;
}
}
return null;
}
//#endregion
//#region node_modules/@openclaw/fs-safe/dist/pinned-python.js
const PINNED_PYTHON_WORKER_SOURCE = String.raw`
import base64, errno, json, os, secrets, stat, sys
DIR_FLAGS = os.O_RDONLY
if hasattr(os, "O_DIRECTORY"):
DIR_FLAGS |= os.O_DIRECTORY
if hasattr(os, "O_NOFOLLOW"):
DIR_FLAGS |= os.O_NOFOLLOW
READ_FLAGS = os.O_RDONLY
if hasattr(os, "O_NONBLOCK"):
READ_FLAGS |= os.O_NONBLOCK
if hasattr(os, "O_NOFOLLOW"):
READ_FLAGS |= os.O_NOFOLLOW
WRITE_FLAGS = os.O_WRONLY | os.O_CREAT | os.O_EXCL
if hasattr(os, "O_NOFOLLOW"):
WRITE_FLAGS |= os.O_NOFOLLOW
def split_relative(value):
if value in ("", "."):
return []
if "\x00" in value or value.startswith("/") or value.startswith("//"):
raise OSError(errno.EPERM, "invalid relative path")
if value.startswith("..\\"):
raise OSError(errno.EPERM, "path traversal is not allowed")
parts = [part for part in value.split("/") if part and part != "."]
for part in parts:
if part == "..":
raise OSError(errno.EPERM, "path traversal is not allowed")
return parts
def open_dir(path_value, dir_fd=None):
return os.open(path_value, DIR_FLAGS, dir_fd=dir_fd)
def walk_dir(root_fd, segments, mkdir_enabled=False):
current_fd = os.dup(root_fd)
try:
for segment in segments:
try:
next_fd = open_dir(segment, dir_fd=current_fd)
except FileNotFoundError:
if not mkdir_enabled:
raise
os.mkdir(segment, 0o777, dir_fd=current_fd)
next_fd = open_dir(segment, dir_fd=current_fd)
os.close(current_fd)
current_fd = next_fd
return current_fd
except Exception:
os.close(current_fd)
raise
def parent_and_basename(root_fd, relative):
segments = split_relative(relative)
if not segments:
raise OSError(errno.EPERM, "operation requires a non-root path")
parent_fd = walk_dir(root_fd, segments[:-1])
return parent_fd, segments[-1]
def encode_stat(st):
mode = st.st_mode
return {
"dev": st.st_dev,
"gid": st.st_gid,
"ino": st.st_ino,
"isDirectory": stat.S_ISDIR(mode),
"isFile": stat.S_ISREG(mode),
"isSymbolicLink": stat.S_ISLNK(mode),
"mode": mode,
"mtimeMs": st.st_mtime * 1000,
"nlink": st.st_nlink,
"size": st.st_size,
"uid": st.st_uid,
}
def reject_unsafe_endpoint(st):
mode = st.st_mode
if stat.S_ISLNK(mode):
raise OSError(errno.ELOOP, "symlink endpoint is not allowed")
if stat.S_ISREG(mode) and st.st_nlink > 1:
raise OSError(errno.EPERM, "hardlinked file endpoint is not allowed")
def copy_bytes(source_fd, dest_fd):
while True:
chunk = os.read(source_fd, 65536)
if not chunk:
break
view = memoryview(chunk)
while view:
written = os.write(dest_fd, view)
if written <= 0:
raise OSError(errno.EIO, "short write")
view = view[written:]
def write_all(fd, data):
view = memoryview(data)
while view:
written = os.write(fd, view)
if written <= 0:
raise OSError(errno.EIO, "short write")
view = view[written:]
def link_unsupported(exc):
unsupported = (errno.EPERM, errno.EOPNOTSUPP, getattr(errno, "ENOTSUP", errno.EOPNOTSUPP))
return getattr(exc, "errno", None) in unsupported
def link_no_replace(name, new_name, source_fd, target_fd):
linked = False
try:
os.link(name, new_name, src_dir_fd=source_fd, dst_dir_fd=target_fd, follow_symlinks=False)
linked = True
os.unlink(name, dir_fd=source_fd)
except Exception:
if linked:
try: os.unlink(new_name, dir_fd=target_fd)
except FileNotFoundError: pass
raise
os.fsync(source_fd)
if source_fd != target_fd:
os.fsync(target_fd)
def copy_file_no_replace(source_parent_fd, source_name, target_parent_fd, basename, mode, expected=None, unlink_source=False):
source_fd = os.open(source_name, READ_FLAGS, dir_fd=source_parent_fd)
dest_fd = None; success = False; dest_stat = None
try:
if expected is not None:
source_stat = os.fstat(source_fd)
if source_stat.st_dev != expected.st_dev or source_stat.st_ino != expected.st_ino:
raise RuntimeError("fs-safe-source-mismatch")
dest_fd = os.open(basename, WRITE_FLAGS, mode, dir_fd=target_parent_fd)
copy_bytes(source_fd, dest_fd)
os.fsync(dest_fd)
dest_stat = os.fstat(dest_fd)
success = True
finally:
os.close(source_fd)
if dest_fd is not None:
os.close(dest_fd)
if dest_fd is not None and not success:
try: os.unlink(basename, dir_fd=target_parent_fd)
except FileNotFoundError: pass
if unlink_source:
try:
os.unlink(source_name, dir_fd=source_parent_fd)
except Exception:
try: os.unlink(basename, dir_fd=target_parent_fd)
except FileNotFoundError: pass
raise
return dest_stat
def same_identity(left, right):
return left.st_dev == right.st_dev and left.st_ino == right.st_ino
def verify_temp_name(parent_fd, temp_name, expected_stat):
current_stat = os.lstat(temp_name, dir_fd=parent_fd)
if stat.S_ISLNK(current_stat.st_mode) or not same_identity(current_stat, expected_stat):
raise RuntimeError("fs-safe-temp-mismatch")
def verify_committed_temp(parent_fd, basename, expected_stat):
final_stat = os.lstat(basename, dir_fd=parent_fd)
if not stat.S_ISLNK(final_stat.st_mode) and same_identity(final_stat, expected_stat):
return final_stat
try: os.unlink(basename, dir_fd=parent_fd)
except FileNotFoundError: pass
raise RuntimeError("fs-safe-temp-mismatch")
def commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, expected_stat):
verify_temp_name(parent_fd, temp_name, expected_stat)
if overwrite:
os.replace(temp_name, basename, src_dir_fd=parent_fd, dst_dir_fd=parent_fd)
return verify_committed_temp(parent_fd, basename, expected_stat)
else:
try:
os.link(temp_name, basename, src_dir_fd=parent_fd, dst_dir_fd=parent_fd, follow_symlinks=False)
final_stat = verify_committed_temp(parent_fd, basename, expected_stat)
os.unlink(temp_name, dir_fd=parent_fd)
return final_stat
except OSError as exc:
if not link_unsupported(exc):
raise
return copy_file_no_replace(parent_fd, temp_name, parent_fd, basename, mode, expected_stat, True)
def assert_expected_root(root_fd, payload):
if "rootDev" in payload or "rootIno" in payload:
root_stat = os.fstat(root_fd)
if root_stat.st_dev != int(payload["rootDev"]) or root_stat.st_ino != int(payload["rootIno"]):
raise RuntimeError("fs-safe-root-mismatch")
def stat_path(root_fd, payload):
relative = payload.get("relativePath", "")
segments = split_relative(relative)
if not segments:
return encode_stat(os.fstat(root_fd))
parent_fd, basename = parent_and_basename(root_fd, relative)
try:
st = os.lstat(basename, dir_fd=parent_fd)
if payload.get("rejectSymlink", True) and stat.S_ISLNK(st.st_mode):
raise OSError(errno.ELOOP, "symlink endpoint is not allowed")
return encode_stat(st)
finally:
os.close(parent_fd)
def readdir_path(root_fd, payload):
dir_fd = walk_dir(root_fd, split_relative(payload.get("relativePath", "")))
try:
names = sorted(os.listdir(dir_fd))
if not payload.get("withFileTypes", False):
return names
entries = []
for name in names:
st = os.lstat(name, dir_fd=dir_fd)
entry = encode_stat(st)
entry["name"] = name
entries.append(entry)
return entries
finally:
os.close(dir_fd)
def mkdirp_path(root_fd, payload):
dir_fd = walk_dir(root_fd, split_relative(payload.get("relativePath", "")), mkdir_enabled=True)
os.close(dir_fd); return None
def remove_tree(parent_fd, basename):
st = os.lstat(basename, dir_fd=parent_fd)
if stat.S_ISDIR(st.st_mode) and not stat.S_ISLNK(st.st_mode):
dir_fd = open_dir(basename, dir_fd=parent_fd)
try:
for child in os.listdir(dir_fd):
remove_tree(dir_fd, child)
finally:
os.close(dir_fd)
os.rmdir(basename, dir_fd=parent_fd)
else:
os.unlink(basename, dir_fd=parent_fd)
def remove_path(root_fd, payload):
parent_fd, basename = parent_and_basename(root_fd, payload.get("relativePath", ""))
try:
try:
st = os.lstat(basename, dir_fd=parent_fd)
except FileNotFoundError:
if payload.get("force", True):
return None
raise
if stat.S_ISDIR(st.st_mode) and not stat.S_ISLNK(st.st_mode):
if payload.get("recursive", False):
remove_tree(parent_fd, basename)
else:
os.rmdir(basename, dir_fd=parent_fd)
else:
os.unlink(basename, dir_fd=parent_fd)
return None
finally:
os.close(parent_fd)
def rename_path(root_fd, payload):
from_parent_fd, from_base = parent_and_basename(root_fd, payload["from"])
to_parent_fd, to_base = parent_and_basename(root_fd, payload["to"])
try:
from_stat = os.lstat(from_base, dir_fd=from_parent_fd)
reject_unsafe_endpoint(from_stat)
overwrite = payload.get("overwrite", True)
if not overwrite and stat.S_ISREG(from_stat.st_mode):
try:
link_no_replace(from_base, to_base, from_parent_fd, to_parent_fd)
except OSError as exc:
if not link_unsupported(exc):
raise
copy_file_no_replace(from_parent_fd, from_base, to_parent_fd, to_base, stat.S_IMODE(from_stat.st_mode), from_stat, True)
return None
if not overwrite and stat.S_ISDIR(from_stat.st_mode):
raise RuntimeError("fs-safe-directory-noreplace-unsupported")
if not overwrite:
try:
os.lstat(to_base, dir_fd=to_parent_fd)
raise FileExistsError(errno.EEXIST, "destination exists", to_base)
except FileNotFoundError:
pass
os.rename(from_base, to_base, src_dir_fd=from_parent_fd, dst_dir_fd=to_parent_fd)
os.fsync(from_parent_fd)
if from_parent_fd != to_parent_fd:
os.fsync(to_parent_fd)
return None
finally:
os.close(from_parent_fd)
os.close(to_parent_fd)
def create_temp_file(parent_fd, basename, mode):
prefix = "." + basename + "."
for _ in range(128):
candidate = prefix + secrets.token_hex(6) + ".tmp"
try:
fd = os.open(candidate, WRITE_FLAGS, mode, dir_fd=parent_fd)
return candidate, fd
except FileExistsError:
continue
raise RuntimeError("failed to allocate pinned temp file")
def write_path(root_fd, payload):
parent_fd = walk_dir(root_fd, split_relative(payload.get("relativeParentPath", "")), bool(payload.get("mkdir", True)))
temp_fd = None
temp_name = None
basename = payload["basename"]
mode = int(payload.get("mode", 0o600))
overwrite = bool(payload.get("overwrite", True))
max_bytes = int(payload.get("maxBytes", -1))
data = base64.b64decode(payload.get("base64", ""))
try:
if max_bytes >= 0 and len(data) > max_bytes:
raise RuntimeError("fs-safe-too-large:%d:%d" % (max_bytes, len(data)))
if not overwrite:
try:
os.lstat(basename, dir_fd=parent_fd)
raise FileExistsError(errno.EEXIST, "destination exists", basename)
except FileNotFoundError:
pass
temp_name, temp_fd = create_temp_file(parent_fd, basename, mode)
os.fchmod(temp_fd, mode)
write_all(temp_fd, data)
os.fsync(temp_fd)
temp_stat = os.fstat(temp_fd)
os.close(temp_fd)
temp_fd = None
result_stat = commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, temp_stat)
temp_name = None
os.fsync(parent_fd)
return {"dev": result_stat.st_dev, "ino": result_stat.st_ino}
finally:
if temp_fd is not None:
os.close(temp_fd)
if temp_name is not None:
try:
os.unlink(temp_name, dir_fd=parent_fd)
except FileNotFoundError:
pass
os.close(parent_fd)
def copy_path(root_fd, payload):
source_fd = os.open(payload["sourcePath"], READ_FLAGS)
parent_fd = None
temp_fd = None
temp_name = None
try:
source_stat = os.fstat(source_fd)
if not stat.S_ISREG(source_stat.st_mode):
raise RuntimeError("fs-safe-not-file")
if source_stat.st_dev != int(payload["sourceDev"]) or source_stat.st_ino != int(payload["sourceIno"]):
raise RuntimeError("fs-safe-source-mismatch")
basename = payload["basename"]
mode = int(payload.get("mode", 0o600))
overwrite = bool(payload.get("overwrite", True))
max_bytes = int(payload.get("maxBytes", -1))
if max_bytes >= 0 and source_stat.st_size > max_bytes:
raise RuntimeError("fs-safe-too-large:%d:%d" % (max_bytes, source_stat.st_size))
parent_fd = walk_dir(root_fd, split_relative(payload.get("relativeParentPath", "")), bool(payload.get("mkdir", True)))
temp_name, temp_fd = create_temp_file(parent_fd, basename, mode)
os.fchmod(temp_fd, mode)
written_bytes = 0
while True:
chunk = os.read(source_fd, 65536)
if not chunk:
break
written_bytes += len(chunk)
if max_bytes >= 0 and written_bytes > max_bytes:
raise RuntimeError("fs-safe-too-large:%d:%d" % (max_bytes, written_bytes))
view = memoryview(chunk)
while view:
written = os.write(temp_fd, view)
if written <= 0:
raise OSError(errno.EIO, "short write")
view = view[written:]
os.fsync(temp_fd)
temp_stat = os.fstat(temp_fd)
os.close(temp_fd)
temp_fd = None
result_stat = commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, temp_stat)
temp_name = None
os.fsync(parent_fd)
return {"dev": result_stat.st_dev, "ino": result_stat.st_ino}
finally:
os.close(source_fd)
if temp_fd is not None:
os.close(temp_fd)
if temp_name is not None and parent_fd is not None:
try:
os.unlink(temp_name, dir_fd=parent_fd)
except FileNotFoundError:
pass
if parent_fd is not None:
os.close(parent_fd)
def run_operation(operation, root_path, payload):
root_fd = open_dir(root_path)
try:
assert_expected_root(root_fd, payload)
if operation == "stat":
return stat_path(root_fd, payload)
if operation == "readdir":
return readdir_path(root_fd, payload)
if operation == "mkdirp":
return mkdirp_path(root_fd, payload)
if operation == "remove":
return remove_path(root_fd, payload)
if operation == "rename":
return rename_path(root_fd, payload)
if operation == "write":
return write_path(root_fd, payload)
if operation == "copy":
return copy_path(root_fd, payload)
raise RuntimeError("unknown operation: " + operation)
finally:
os.close(root_fd)
for line in sys.stdin:
try:
request = json.loads(line)
result = run_operation(request["operation"], request["rootPath"], request.get("payload") or {})
response = {"id": request["id"], "ok": True, "result": result}
except Exception as exc:
response = {
"id": request.get("id") if isinstance(locals().get("request"), dict) else None,
"ok": False,
"code": exc.__class__.__name__,
"errno": getattr(exc, "errno", None),
"message": str(exc),
}
print(json.dumps(response, separators=(",", ":")), flush=True)
`;
let nextRequestId = 1;
let worker = null;
const PYTHON_CANDIDATE_DEFAULTS = [
"/usr/bin/python3",
"/opt/homebrew/bin/python3",
"/usr/local/bin/python3"
];
function canExecute(binPath) {
try {
fs.accessSync(binPath, fs.constants.X_OK);
return true;
} catch {
return false;
}
}
function resolvePython() {
const configured = getFsSafePythonConfig().pythonPath;
if (configured) return configured;
for (const candidate of PYTHON_CANDIDATE_DEFAULTS) if (canExecute(candidate)) return candidate;
return "python3";
}
function assertPinnedHelperSupported() {
if (process.platform === "win32") throw new FsSafeError("unsupported-platform", "fd-relative pinned filesystem operations are not available on Windows");
if (getFsSafePythonConfig().mode === "off") throw new FsSafeError("helper-unavailable", "Python helper is disabled");
}
function isSpawnUnavailable(error) {
if (!(error instanceof Error)) return false;
const maybeErrno = error;
return typeof maybeErrno.syscall === "string" && maybeErrno.syscall.startsWith("spawn") && [
"EACCES",
"ENOENT",
"ENOEXEC"
].includes(maybeErrno.code ?? "");
}
function mapWorkerError(response) {
const code = typeof response.code === "string" ? response.code : "";
const errno = typeof response.errno === "number" ? response.errno : void 0;
const message = typeof response.message === "string" && response.message ? response.message : "pinned helper failed";
const tooLarge = message.match(/fs-safe-too-large:(\d+):(\d+)/);
if (tooLarge) {
const [, limit, got] = tooLarge;
return new FsSafeError("too-large", `file exceeds limit of ${limit} bytes (got at least ${got})`);
}
if (message.includes("fs-safe-not-file")) return new FsSafeError("not-file", "not a file");
if (message.includes("fs-safe-source-mismatch")) return new FsSafeError("path-mismatch", "source path changed during copy");
if (message.includes("fs-safe-temp-mismatch")) return new FsSafeError("path-mismatch", "temp path changed during write");
if (message.includes("fs-safe-root-mismatch")) return new FsSafeError("path-mismatch", "root path changed during operation");
if (message.includes("fs-safe-directory-noreplace-unsupported")) return new FsSafeError("invalid-path", "directory moves require overwrite: true");
if (code === "FileNotFoundError" || errno === 2) return new FsSafeError("not-found", "file not found");
if (code === "FileExistsError" || errno === 17) return new FsSafeError("already-exists", message);
if (errno === 39) return new FsSafeError("not-empty", "directory is not empty");
if (errno === 1 || errno === 13 || errno === 21) return new FsSafeError("not-removable", "path is not removable under root");
if (code === "NotADirectoryError" || code === "OSError" || errno === 20 || errno === 40) return new FsSafeError("path-alias", message);
return new FsSafeError("helper-failed", message);
}
function rejectPending(error, targetWorker = worker) {
if (!targetWorker || worker !== targetWorker) return;
setWorkerRef(targetWorker, false);
for (const pending of targetWorker.pending.values()) pending.reject(error);
targetWorker.pending.clear();
worker = null;
}
function handleWorkerLine(currentWorker, line) {
if (worker !== currentWorker || !line.trim()) return;
let decoded;
try {
decoded = JSON.parse(line);
} catch {
rejectPending(new FsSafeError("helper-failed", `pinned helper returned invalid JSON: ${line}`), currentWorker);
return;
}
if (typeof decoded !== "object" || decoded === null || !("id" in decoded)) {
rejectPending(new FsSafeError("helper-failed", "pinned helper returned invalid response"), currentWorker);
return;
}
const response = decoded;
const id = typeof response.id === "number" ? response.id : void 0;
if (id === void 0) return;
const pending = currentWorker.pending.get(id);
if (!pending) return;
currentWorker.pending.delete(id);
if (currentWorker.pending.size === 0) setWorkerRef(currentWorker, false);
if (response.ok === true) {
pending.resolve(response.result);
return;
}
pending.reject(mapWorkerError(decoded));
}
function getWorker() {
assertPinnedHelperSupported();
if (worker) return worker;
const child = spawn(resolvePython(), [
"-u",
"-c",
PINNED_PYTHON_WORKER_SOURCE
], { stdio: [
"pipe",
"pipe",
"pipe"
] });
const currentWorker = {
child,
pending: /* @__PURE__ */ new Map(),
stderr: "",
stdoutBuffer: ""
};
worker = currentWorker;
child.stdout.setEncoding("utf8");
child.stderr.setEncoding("utf8");
child.stdout.on("data", (chunk) => {
if (worker !== currentWorker) return;
currentWorker.stdoutBuffer += chunk;
for (;;) {
const newline = currentWorker.stdoutBuffer.indexOf("\n");
if (newline < 0) break;
const line = currentWorker.stdoutBuffer.slice(0, newline);
currentWorker.stdoutBuffer = currentWorker.stdoutBuffer.slice(newline + 1);
handleWorkerLine(currentWorker, line);
}
});
child.stderr.on("data", (chunk) => {
if (worker === currentWorker) currentWorker.stderr = `${currentWorker.stderr}${chunk}`.slice(-4096);
});
child.once("error", (error) => {
rejectPending(isSpawnUnavailable(error) ? new FsSafeError("helper-unavailable", "Python helper is unavailable", { cause: error }) : error instanceof Error ? error : new Error(String(error)), currentWorker);
});
child.once("close", (code, signal) => {
rejectPending(new FsSafeError("helper-failed", currentWorker.stderr.trim() || `pinned helper exited with code ${code ?? "null"} (${signal ?? "?"})`), currentWorker);
});
process.once("exit", () => {
child.kill("SIGTERM");
});
setWorkerRef(currentWorker, false);
return currentWorker;
}
function setRefable(value, ref) {
if (!value) return;
value[ref ? "ref" : "unref"]?.();
}
function setWorkerRef(currentWorker, ref) {
setRefable(currentWorker.child, ref);
setRefable(currentWorker.child.stdin, ref);
setRefable(currentWorker.child.stdout, ref);
setRefable(currentWorker.child.stderr, ref);
}
async function runPinnedPythonOperation(params) {
const requestId = nextRequestId++;
const currentWorker = getWorker();
if (typeof currentWorker.child.stdin?.write !== "function") throw new FsSafeError("helper-unavailable", "Python helper stdin is unavailable");
setWorkerRef(currentWorker, true);
return await new Promise((resolve, reject) => {
currentWorker.pending.set(requestId, {
reject,
resolve: (value) => resolve(value)
});
const request = JSON.stringify({
id: requestId,
operation: params.operation,
rootPath: params.rootPath,
payload: params.payload
});
currentWorker.child.stdin.write(`${request}\n`, (error) => {
if (error) {
currentWorker.pending.delete(requestId);
if (currentWorker.pending.size === 0) setWorkerRef(currentWorker, false);
reject(error);
}
});
});
}
function assertPinnedPythonOperationAvailable() {
if (typeof getWorker().child.stdin?.write !== "function") throw new FsSafeError("helper-unavailable", "Python helper stdin is unavailable");
}
function validatePinnedOperationPayload(payload) {
if (typeof payload.relativePath === "string") validatePinnedRelativePath(payload.relativePath);
if (typeof payload.relativeParentPath === "string") validatePinnedRelativePath(payload.relativeParentPath);
if (typeof payload.from === "string") validatePinnedRelativePath(payload.from);
if (typeof payload.to === "string") validatePinnedRelativePath(payload.to);
}
function validatePinnedRelativePath(relativePath) {
if (relativePath.length === 0 || relativePath === ".") return;
if (relativePath.includes("\0")) throw new FsSafeError("invalid-path", "relative path contains a NUL byte");
if (relativePath.startsWith("/") || relativePath.startsWith("//") || relativePath === ".." || relativePath.startsWith("../") || relativePath.startsWith("..\\")) throw new FsSafeError("invalid-path", "relative path must not escape root");
for (const segment of relativePath.split("/")) if (segment === "..") throw new FsSafeError("invalid-path", "relative path must not contain '..'");
}
//#endregion
//#region node_modules/@openclaw/fs-safe/dist/pinned-helper.js
async function runPinnedHelper(operation, rootDir, payload) {
validatePinnedOperationPayload(payload);
return await runPinnedPythonOperation({
operation,
rootPath: rootDir,
payload
});
}
async function helperStat(rootDir, relativePath) {
return await runPinnedHelper("stat", rootDir, { relativePath });
}
async function helperReaddir(rootDir, relativePath, withFileTypes) {
return await runPinnedHelper("readdir", rootDir, {
relativePath,
withFileTypes
});
}
//#endregion
//#region node_modules/@openclaw/fs-safe/dist/pinned-path.js
function isPinnedPathHelperSpawnError(error) {
return canFallbackFromPythonError(error);
}
async function runPinnedPathHelper(params) {
try {
await runPinnedHelper(params.operation, params.rootPath, { relativePath: params.relativePath });
} catch (error) {
if (error instanceof FsSafeError) throw error;
throw new FsSafeError("helper-failed", "pinned path helper failed", { cause: error instanceof Error ? error : void 0 });
}
}
//#endregion
//#region node_modules/@openclaw/fs-safe/dist/pinned-write.js
function byteLength(input, encoding) {
return typeof input === "string" ? Buffer.byteLength(input, encoding ?? "utf8") : input.byteLength;
}
function assertSafeBasename(basename) {
if (!basename || basename === "." || basename === ".." || basename.includes("/") || basename.includes("\0")) throw new FsSafeError("invalid-path", "invalid target path");
}
function assertWithinMaxBytes(bytes, maxBytes) {
if (maxBytes !== void 0 && bytes > maxBytes) throw new FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
}
async function writeStreamToHandle(stream, handle, maxBytes) {
let bytes = 0;
for await (const chunk of stream) {
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
bytes += buffer.byteLength;
assertWithinMaxBytes(bytes, maxBytes);
let offset = 0;
while (offset < buffer.byteLength) {
const { bytesWritten } = await handle.write(buffer, offset, buffer.byteLength - offset);
if (bytesWritten <= 0) throw new FsSafeError("helper-failed", "fallback stream write made no progress");
offset += bytesWritten;
}
}
}
async function inputToBase64(input, maxBytes) {
if (input.kind === "buffer") {
assertWithinMaxBytes(byteLength(input.data, input.encoding), maxBytes);
return (typeof input.data === "string" ? Buffer.from(input.data, input.encoding ?? "utf8") : input.data).toString("base64");
}
const chunks = [];
let bytes = 0;
for await (const chunk of input.stream) {
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
bytes += buffer.byteLength;
assertWithinMaxBytes(bytes, maxBytes);
chunks.push(buffer);
}
return Buffer.concat(chunks, bytes).toString("base64");
}
async function runPinnedWriteHelper(params) {
assertSafeBasename(params.basename);
validatePinnedOperationPayload({ relativeParentPath: params.relativeParentPath });
if (getFsSafePythonConfig().mode === "off") return await runPinnedWriteFallback(params);
if (params.input.kind === "stream") try {
assertPinnedPythonOperationAvailable();
} catch (error) {
if (canFallbackFromPythonError(error)) return await runPinnedWriteFallback(params);
throw error;
}
const payload = {
base64: await inputToBase64(params.input, params.maxBytes),
basename: params.basename,
maxBytes: params.maxBytes ?? -1,
mkdir: params.mkdir,
mode: params.mode || 384,
overwrite: params.overwrite !== false,
relativeParentPath: params.relativeParentPath,
...params.rootIdentity ? {
rootDev: params.rootIdentity.dev,
rootIno: params.rootIdentity.ino
} : {}
};
try {
return await runPinnedPythonOperation({
operation: "write",
rootPath: params.rootPath,
payload
});
} catch (error) {
if (canFallbackFromPythonError(error)) return await runPinnedWriteFallback(params);
throw error;
}
}
async function runPinnedCopyHelper(params) {
assertSafeBasename(params.basename);
validatePinnedOperationPayload({ relativeParentPath: params.relativeParentPath });
return await runPinnedPythonOperation({
operation: "copy",
rootPath: params.rootPath,
payload: {
basename: params.basename,
maxBytes: params.maxBytes ?? -1,
mkdir: params.mkdir,
mode: params.mode || 384,
overwrite: params.overwrite !== false,
relativeParentPath: params.relativeParentPath,
...params.rootIdentity ? {
rootDev: params.rootIdentity.dev,
rootIno: params.rootIdentity.ino
} : {},
sourceDev: params.sourceIdentity.dev,
sourceIno: params.sourceIdentity.ino,
sourcePath: params.sourcePath
}
});
}
async function runPinnedWriteFallback(params) {
const parentPath = params.relativeParentPath ? path.join(params.rootPath, ...params.relativeParentPath.split("/")) : params.rootPath;
if (params.mkdir) await mkdirPathComponentsWithGuards({
rootReal: params.rootPath,
targetPath: parentPath
});
const parentGuard = params.mkdir ? await createAsyncDirectoryGuard(parentPath) : await createNearestExistingDirectoryGuard(params.rootPath, parentPath);
const targetPath = path.join(parentPath, params.basename);
if (params.overwrite === false) {
let handle = await withAsyncDirectoryGuards([parentGuard], async () => await fs$1.open(targetPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL, params.mode), { onPostGuardFailure: async (openedHandle) => {
await openedHandle.close().catch(() => void 0);
} });
let created = true;
try {
if (params.input.kind === "buffer") {
assertWithinMaxBytes(byteLength(params.input.data, params.input.encoding), params.maxBytes);
if (typeof params.input.data === "string") await handle.writeFile(params.input.data, params.input.encoding ?? "utf8");
else await handle.writeFile(params.input.data);
} else await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
const stat = await handle.stat();
created = false;
return {
dev: stat.dev,
ino: stat.ino
};
} finally {
await handle.close().catch(() => void 0);
if (created) await fs$1.rm(targetPath, { force: true }).catch(() => void 0);
}
}
const tempPath = path.join(parentPath, `.${params.basename}.${randomUUID()}.fallback.tmp`);
const tempFlags = fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | (process.platform !== "win32" && "O_NOFOLLOW" in fs.constants ? fs.constants.O_NOFOLLOW : 0);
let handle;
let tempStat;
let targetStat;
let renamed = false;
try {
handle = await fs$1.open(tempPath, tempFlags, params.mode);
if (params.input.kind === "buffer") {
assertWithinMaxBytes(byteLength(params.input.data, params.input.encoding), params.maxBytes);
if (typeof params.input.data === "string") await handle.writeFile(params.input.data, params.input.encoding ?? "utf8");
else await handle.writeFile(params.input.data);
} else await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
tempStat = await handle.stat();
const tempPathStat = await fs$1.lstat(tempPath);
if (tempPathStat.isSymbolicLink() || !sameFileIdentity(tempPathStat, tempStat)) throw new FsSafeError("path-mismatch", "fallback temp path changed during write");
const expectedTempStat = tempStat;
await handle.close().catch(() => void 0);
handle = void 0;
await withAsyncDirectoryGuards([parentGuard], async () => {
await fs$1.rename(tempPath, targetPath);
renamed = true;
targetStat = await fs$1.lstat(targetPath);
if (targetStat.isSymbolicLink() || !sameFileIdentity(targetStat, expectedTempStat)) throw new FsSafeError("path-mismatch", "fallback target changed during write");
});
} catch (error) {
await handle?.close().catch(() => void 0);
if (!renamed) await fs$1.rm(tempPath, { force: true }).catch(() => void 0);
throw error;
}
if (!targetStat) throw new FsSafeError("path-mismatch", "fallback target was not verified");
return {
dev: targetStat.dev,
ino: targetStat.ino
};
}
//#endregion
//#region node_modules/@openclaw/fs-safe/dist/path-policy.js
const PATH_ALIAS_POLICIES = ROOT_PATH_ALIAS_POLICIES;
async function assertNoPathAliasEscape(params) {
const resolved = await resolveRootPath({
absolutePath: params.absolutePath,
rootPath: params.rootPath,
boundaryLabel: params.boundaryLabel,
policy: params.policy
});
if (params.policy?.allowFinalSymlinkForUnlink === true && resolved.kind === "symlink") return;
await assertNoHardlinkedFinalPath({
filePath: resolved.absolutePath,
root: resolved.rootPath,
boundaryLabel: params.boundaryLabel,
allowFinalHardlinkForUnlink: params.policy?.allowFinalHardlinkForUnlink
});
}
async function assertNoHardlinkedFinalPath(params) {
if (params.allowFinalHardlinkForUnlink) return;
let stat;
try {
stat = await fs$1.stat(params.filePath);
} catch (err) {
if (isNotFoundPathError(err)) return;
throw err;
}
if (!stat.isFile()) return;
if (stat.nlink > 1) throw new Error(`Hardlinked path is not allowed under ${params.boundaryLabel} (${shortPath(params.root)}): ${shortPath(params.filePath)}`);
}
function shortPath(value) {
if (value.startsWith(os.homedir())) return `~${value.slice(os.homedir().length)}`;
return value;
}
//#endregion
//#region node_modules/@openclaw/fs-safe/dist/read-opened-file.js
async function readOpenedFileSafely(params) {
if (params.maxBytes !== void 0 && params.opened.stat.size > params.maxBytes) throw new FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${params.opened.stat.size})`);
const buffer = await params.opened.handle.readFile();
if (params.maxBytes !== void 0 && buffer.byteLength > params.maxBytes) throw new FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${buffer.byteLength})`);
return {
buffer,
realPath: params.opened.realPath,
stat: params.opened.stat
};
}
//#endregion
//#region node_modules/@openclaw/fs-safe/dist/path-stat.js
function pathStatFromStats(stat) {
return {
dev: Number(stat.dev),
gid: Number(stat.gid),
ino: Number(stat.ino),
isDirectory: stat.isDirectory(),
isFile: stat.isFile(),
isSymbolicLink: stat.isSymbolicLink(),
mode: stat.mode,
mtimeMs: stat.mtimeMs,
nlink: stat.nlink,
size: stat.size,
uid: stat.uid
};
}
//#endregion
//#region node_modules/@openclaw/fs-safe/dist/root-context.js
const ensureTrailingSep = (value) => value.endsWith(path.sep) ? value : value + path.sep;
function assertValidRootRelativePath(relativePath) {
assertNoNulPathInput(relativePath, "relative path contains a NUL byte");
}
let cachedHomePath;
async function expandRelativePathWithHome(relativePath) {
const rawHome = process.env.HOME || process.env.USERPROFILE || os.homedir();
if (cachedHomePath?.raw !== rawHome) {
let realHome = rawHome;
try {
realHome = await fs$1.realpath(rawHome);
} catch {}
cachedHomePath = {
raw: rawHome,
real: realHome
};
}
return expandHomePrefix(relativePath, { home: cachedHomePath.real });
}
async function resolveRootContext(rootDir) {
assertNoNulPathInput(rootDir, "root dir contains a NUL byte");
let rootReal;
try {
rootReal = await fs$1.realpath(rootDir);
if (!(await fs$1.stat(rootReal)).isDirectory()) throw new FsSafeError("invalid-path", "root dir is not a directory");
} catch (err) {
if (err instanceof FsSafeError) throw err;
if (isNotFoundPathError(err)) throw new FsSafeError("not-found", "root dir not found");
throw err;
}
return {
rootDir: path.resolve(rootDir),
rootReal,
rootWithSep: ensureTrailingSep(rootReal)
};
}
async function resolvePathInRoot(root, relativePath) {
assertValidRootRelativePath(relativePath);
const expanded = await expandRelativePathWithHome(relativePath);
const resolved = path.resolve(root.rootWithSep, expanded);
if (!isPathInside(root.rootWithSep, resolved)) throw new FsSafeError("outside-workspace", "file is outside workspace root");
return {
rootReal: root.rootReal,
rootWithSep: root.rootWithSep,
resolved
};
}
//#endregion
//#region node_modules/@openclaw/fs-safe/dist/root-errors.js
function isAlreadyExistsError(error) {
return hasNodeErrorCode(error, "EEXIST") || /File exists|EEXIST/i.test(String(error));
}
function normalizePinnedWriteError(error) {
if (error instanceof FsSafeError) return error;
return new FsSafeError("invalid-path", "path is not a regular file under root", { cause: error instanceof Error ? error : void 0 });
}
function normalizePinnedPathError(error) {
if (error instanceof FsSafeError) return error;
return new FsSafeError("path-alias", "path is not under root", { cause: error instanceof Error ? error : void 0 });
}
function getFsSafeTestHooks() {}
//#endregion
//#region node_modules/@openclaw/fs-safe/dist/root-impl.js
function logWarn(message) {
if (process.env.FS_SAFE_DEBUG_WARNINGS === "1") console.warn(message);
}
const SUPPORTS_NOFOLLOW = process.platform !== "win32" && "O_NOFOLLOW" in constants;
const NONBLOCK_OPEN_FLAG = "O_NONBLOCK" in constants ? constants.O_NONBLOCK : 0;
const OPEN_READ_FLAGS = constants.O_RDONLY | (SUPPORTS_NOFOLLOW ? constants.O_NOFOLLOW : 0);
const OPEN_READ_NONBLOCK_FLAGS = OPEN_READ_FLAGS | NONBLOCK_OPEN_FLAG;
const OPEN_READ_FOLLOW_FLAGS = constants.O_RDONLY;
const OPEN_READ_FOLLOW_NONBLOCK_FLAGS = OPEN_READ_FOLLOW_FLAGS | NONBLOCK_OPEN_FLAG;
const OPEN_WRITE_EXISTING_FLAGS = constants.O_WRONLY | (SUPPORTS_NOFOLLOW ? constants.O_NOFOLLOW : 0);
const OPEN_WRITE_CREATE_FLAGS = constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (SUPPORTS_NOFOLLOW ? constants.O_NOFOLLOW : 0);
const OPEN_APPEND_EXISTING_FLAGS = constants.O_RDWR | constants.O_APPEND | (SUPPORTS_NOFOLLOW ? constants.O_NOFOLLOW : 0);
const OPEN_APPEND_CREATE_FLAGS = constants.O_RDWR | constants.O_APPEND | constants.O_CREAT | constants.O_EXCL | (SUPPORTS_NOFOLLOW ? constants.O_NOFOLLOW : 0);
const DEFAULT_ROOT_MAX_BYTES = 16 * 1024 * 1024;
function closeHandleForDispose(handle) {
return handle.close().catch(() => void 0);
}
function openResult(params) {
return {
handle: params.handle,
realPath: params.realPath,
stat: params.stat,
[Symbol.asyncDispose]: async () => {
await closeHandleForDispose(params.handle);
}
};
}
async function openVerifiedLocalFile(filePath, options) {
try {
if ((await fs$1.lstat(filePath)).isDirectory()) throw new FsSafeError("not-file", "not a file");
await void 0;
} catch (err) {
if (err instanceof FsSafeError) throw err;
}
let handle;
try {
const openFlags = options?.symlinks === "follow-within-root" ? options?.nonBlockingRead ? OPEN_READ_FOLLOW_NONBLOCK_FLAGS : OPEN_READ_FOLLOW_FLAGS : options?.nonBlockingRead ? OPEN_READ_NONBLOCK_FLAGS : OPEN_READ_FLAGS;
await void 0;
handle = await fs$1.open(filePath, openFlags);
try {
await void 0;
} catch (err) {
await handle.close().catch(() => {});
throw err;
}
} catch (err) {
if (isNotFoundPathError(err)) throw new FsSafeError("not-found", "file not found");
if (isSymlinkOpenError(err)) throw new FsSafeError("symlink", "symlink open blocked", { cause: err });
if (hasNodeErrorCode(err, "EISDIR")) throw new FsSafeError("not-file", "not a file");
throw err;
}
try {
const stat = await handle.stat();
if (!stat.isFile()) throw new FsSafeError("not-file", "not a file");
if (options?.hardlinks === "reject" && stat.nlink > 1) throw new FsSafeError("hardlink", "hardlinked path not allowed");
if (options?.symlinks === "follow-within-root") {
if (!sameFileIdentity(stat, await fs$1.stat(filePath))) throw new FsSafeError("path-mismatch", "path changed during read");
} else {
const pathStat = await fs$1.lstat(filePath);
if (pathStat.isSymbolicLink()) throw new FsSafeError("symlink", "symlink not allowed");
if (!sameFileIdentity(stat, pathStat)) throw new FsSafeError("path-mismatch", "path changed during read");
}
const realPath = await resolveOpenedFileRealPathForHandle(handle, filePath);
const realStat = await fs$1.stat(realPath);
if (options?.hardlinks === "reject" && realStat.nlink > 1) throw new FsSafeError("hardlink", "hardlinked path not allowed");
if (!sameFileIdentity(stat, realStat)) throw new FsSafeError("path-mismatch", "path mismatch");
return openResult({
handle,
realPath,
stat
});
} catch (err) {
await handle.close().catch(() => {});
if (err instanceof FsSafeError) throw err;
if (isNotFoundPathError(err)) throw new FsSafeError("not-found", "file not found");
throw err;
}
}
var RootHandle = class {
rootDir;
rootReal;
rootWithSep;
defaults;
constructor(context, defaults = {}) {
this.rootDir = context.rootDir;
this.rootReal = context.rootReal;
this.rootWithSep = context.rootWithSep;
this.defaults = defaults;
}
get context() {
return {
rootDir: this.rootDir,
rootReal: this.rootReal,
rootWithSep: this.rootWithSep
};
}
async resolve(relativePath) {
return (await resolvePathInRoot(this.context, relativePath)).resolved;
}
async open(relativePath, options = {}) {
return await openFileInRoot(this.context, {
relativePath,
...readDefaults(this.defaults),
...options
});
}
async read(relativePath, options = {}) {
return await readFileInRoot(this.context, {
relativePath,
...readDefaults(this.defaults),
...options
});
}
async readBytes(relativePath, options = {}) {
return (await this.read(relativePath, options)).buffer;
}
async readText(relativePath, options = {}) {
const { encoding = "utf8", ...readOptions } = options;
return (await this.read(relativePath, readOptions)).buffer.toString(encoding);
}
async readJson(relativePath, options = {}) {
return JSON.parse(await this.readText(relativePath, options));
}
async readAbsolute(filePath, options = {}) {
return await readPathInRoot(this.context, {
filePath,
...readDefaults(this.defaults),
...options
});
}
reader(options = {}) {
return async (filePath) => {
return (await this.readAbsolute(filePath, options)).buffer;
};
}
async openWritable(relativePath, options = {}) {
const writeMode = options.writeMode ?? "replace";
return await openWritableFileInRoot(this.context, {
relativePath,
mkdir: this.defaults.mkdir,
mode: this.defaults.mode,
...options,
denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations),
append: writeMode === "append",
truncateExisting: writeMode === "replace"
});
}
async append(relativePath, data, options = {}) {
await appendFileInRoot(this.context, {
relativePath,
data,
mkdir: this.defaults.mkdir,
mode: this.defaults.mode,
...options,
denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations)
});
}
async remove(relativePath, options = {}) {
assertValidRootRelativePath(relativePath);
await removePathInRoot(this.context, {
relativePath,
denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations)
});
}
async mkdir(relativePath, options = {}) {
assertValidRootRelativePath(relativePath);
await mkdirPathInRoot(this.context, {
relativePath,
denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations)
});
}
async ensureRoot(options = {}) {
await mkdirPathInRoot(this.context, {
relativePath: "",
allowRoot: true,
denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations)
});
}
async write(relativePath, data, options = {}) {
await writeFileInRoot(this.context, {
relativePath,
data,
mkdir: this.defaults.mkdir,
mode: this.defaults.mode,
...options,
denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations)
});
}
async create(relativePath, data, options = {}) {
await writeFileInRoot(this.context, {
relativePath,
data,
mkdir: this.defaults.mkdir,
mode: this.defaults.mode,
...options,
denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations),
overwrite: false
});
}
async writeJson(relativePath, data, options = {}) {
const { replacer, space, trailingNewline = true, ...writeOptions } = options;
const json = stringifyJsonDocument(data, replacer, space);
await this.write(relativePath, trailingNewline ? `${json}\n` : json, writeOptions);
}
async createJson(relativePath, data, options = {}) {
const { replacer, space, trailingNewline = true, ...writeOptions } = options;
const json = stringifyJsonDocument(data, replacer, space);
await this.create(relativePath, trailingNewline ? `${json}\n` : json, writeOptions);
}
async copyIn(relativePath, sourcePath, options = {}) {
assertValidRootRelativePath(relativePath);
await copyFileInRoot(this.context, {
sourcePath,
relativePath,
maxBytes: this.defaults.maxBytes,
mkdir: this.defaults.mkdir,
mode: this.defaults.mode,
...options,
denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations)
});
}
async exists(relativePath) {
try {
await this.stat(relativePath);
return true;
} catch (err) {
if (err instanceof FsSafeError && err.code === "not-found") return false;
throw err;
}
}
async stat(relativePath) {
assertValidRootRelativePath(relativePath);
try {
return await helperStat(this.rootReal, relativePath);
} catch (error) {
if (canFallbackFromPythonError(error)) return await statPathFallback(this.context, relativePath);
throw error;
}
}
async list(relativePath, options = {}) {
assertValidRootRelativePath(relativePath);
try {
return options.withFileTypes === true ? await helperReaddir(this.rootReal, relativePath, true) : await helperReaddir(this.rootReal, relativePath, false);
} catch (error) {
if (canFallbackFromPythonError(error)) return await listPathFallback(this.context, relativePath, options.withFileTypes === true);
throw error;
}
}
async move(fromRelative, toRelative, options = {}) {
assertValidRootRelativePath(fromRelative);
assertValidRootRelativePath(toRelative);
const denyMutations = mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations);
await assertMoveMutationAllowed(this.context, {
fromRelative,
toRelative,
denyMutations
});
try {
await runPinnedHelper("rename", this.rootReal, {
from: fromRelative,
overwrite: options.overwrite ?? false,
to: toRelative
});
} catch (error) {
if (canFallbackFromPythonError(error)) {
await movePathFallback(this.context, {
fromRelative,
denyMutations,
overwrite: options.overwrite ?? false,
toRelative
});
return;
}
throw error;
}
}
};
function readDefaults(defaults) {
return {
hardlinks: defaults.hardlinks,
maxBytes: defaults.maxBytes ?? 16777216,
nonBlockingRead: defaults.nonBlockingRead,
symlinks: defaults.symlinks
};
}
async function root(rootDir, defaults = {}) {
return new RootHandle(await resolveRootContext(rootDir), defaults);
}
async function openFileInRoot(root, params) {
const { rootWithSep, resolved } = await resolvePathInRoot(root, params.relativePath);
let opened;
try {
opened = await openVerifiedLocalFile(resolved, {
nonBlockingRead: params.nonBlockingRead,
symlinks: params.symlinks
});
} catch (err) {
if (err instanceof FsSafeError) throw err;
throw err;
}
if (params.hardlinks !== "allow" && opened.stat.nlink > 1) {
await opened.handle.close().catch(() => {});
throw new FsSafeError("hardlink", "hardlinked path not allowed");
}
if (!isPathInside(rootWithSep, opened.realPath)) {
await opened.handle.close().catch(() => {});
throw new FsSafeError("outside-workspace", "file is outside workspace root");
}
return opened;
}
async function readFileInRoot(root, params) {
const opened = await openFileInRoot(root, params);
try {
return await readOpenedFileSafely({
opened,
maxBytes: params.maxBytes
});
} finally {
await opened.handle.close().catch(() => {});
}
}
async function readPathInRoot(root, params) {
const rootDir = root.rootDir;
const candidatePath = path.isAbsolute(params.filePath) ? path.resolve(params.filePath) : path.resolve(rootDir, params.filePath);
return await readFileInRoot(root, {
relativePath: path.relative(rootDir, candidatePath),
hardlinks: params.hardlinks,
maxBytes: params.maxBytes,
nonBlockingRead: params.nonBlockingRead,
symlinks: params.symlinks
});
}
async function readLocalFileSafely(params) {
const opened = await openLocalFileSafely({ filePath: params.filePath });
try {
return await readOpenedFileSafely({
opened,
maxBytes: params.maxBytes
});
} finally {
await opened.handle.close().catch(() => {});
}
}
async function openLocalFileSafely(params) {
assertNoNulPathInput(params.filePath, "file path contains a NUL byte");
return await openVerifiedLocalFile(params.filePath);
}
function emitWriteBoundaryWarning(reason) {
logWarn(`security: fs-safe write boundary warning (${reason})`);
}
function buildAtomicWriteTempPath(targetPath) {
const dir = path.dirname(targetPath);
const base = path.basename(targetPath);
return path.join(dir, `.${base}.${process.pid}.${randomUUID()}.tmp`);
}
function rootWriteQueueKey(root, relativePath) {
return `${root.rootReal}\0${relativePath}`;
}
async function writeTempFileForAtomicReplace(params) {
const tempHandle = await fs$1.open(params.tempPath, OPEN_WRITE_CREATE_FLAGS, params.mode);
try {
if (typeof params.data === "string") await tempHandle.writeFile(params.data, params.encoding ?? "utf8");
else await tempHandle.writeFile(params.data);
return await tempHandle.stat();
} finally {
await tempHandle.close().catch(() => {});
}
}
async function verifyAtomicWriteResult(params) {
const opened = await openVerifiedLocalFile(params.targetPath, { hardlinks: "reject" });
try {
if (!sameFileIdentity(opened.stat, params.expectedIdentity)) throw new FsSafeError("path-mismatch", "path changed during write");
if (!isPathInside(params.root.rootWithSep, opened.realPath)) throw new FsSafeError("outside-workspace", "file is outside workspace root");
} finally {
await opened.handle.close().catch(() => {});
}
}
async function openWritableFileInRoot(root, params) {
const { rootReal, rootWithSep, resolved } = await resolvePathInRoot(root, params.relativePath);
await assertMutationNotDenied(resolved, params.denyMutations);
try {
await assertNoPathAliasEscape({
absolutePath: resolved,
rootPath: rootReal,
boundaryLabel: "root"
});
} catch (err) {
throw new FsSafeError("path-alias", "path alias escape blocked", { cause: err });
}
if (params.mkdir !== false) await withAsyncDirectoryGuards([await createNearestExistingDirectoryGuard(rootReal, path.dirname(resolved))], async () => {
await fs$1.mkdir(path.dirname(resolved), { recursive: true });
});
let ioPath = resolved;
try {
const resolvedRealPath = await fs$1.realpath(resolved);
if (!isPathInside(rootWithSep, resolvedRealPath)) throw new FsSafeError("outside-workspace", "file is outside workspace root");
ioPath = resolvedRealPath;
} catch (err) {
if (err instanceof FsSafeError) throw err;
if (!isNotFoundPathError(err)) throw err;
}
const mode = params.mode ?? 384;
let handle;
let createdForWrite = false;
const existingFlags = params.append ? OPEN_APPEND_EXISTING_FLAGS : OPEN_WRITE_EXISTING_FLAGS;
const createFlags = params.append ? OPEN_APPEND_CREATE_FLAGS : OPEN_WRITE_CREATE_FLAGS;
try {
try {
handle = await fs$1.open(ioPath, existingFlags, mode);
} catch (err) {
if (!isNotFoundPathError(err)) throw err;
handle = await fs$1.open(ioPath, createFlags, mode);
createdForWrite = true;
}
} catch (err) {
if (isNotFoundPathError(err)) throw new FsSafeError("not-found", "file not found");
if (isSymlinkOpenError(err)) throw new FsSafeError("symlink", "symlink open blocked", { cause: err });
if (hasNodeErrorCode(err, "EISDIR")) throw new FsSafeError("not-file", "not a file", { cause: err });
throw err;
}
let realPathForCleanup = null;
try {
const stat = await handle.stat();
if (!stat.isFile()) throw new FsSafeError("invalid-path", "path is not a regular file under root");
if (stat.nlink > 1) throw new FsSafeError("hardlink", "hardlinked path not allowed");
try {
const lstat = await fs$1.lstat(ioPath);
if (lstat.isSymbolicLink() || !lstat.isFile()) throw new FsSafeError(lstat.isSymbolicLink() ? "symlink" : "not-file", "path is not a regular file under root");
if (!sameFileIdentity(stat, lstat)) throw new FsSafeError("path-mismatch", "path changed during write");
} catch (err) {
if (!isNotFoundPathError(err)) throw err;
}
const realPath = await resolveOpenedFileRealPathForHandle(handle, ioPath);
realPathForCleanup = realPath;
const realStat = await fs$1.stat(realPath);
if (!sameFileIdentity(stat, realStat)) throw new FsSafeError("path-mismatch", "path mismatch");
if (realStat.nlink > 1) throw new FsSafeError("hardlink", "hardlinked path not allowed");
if (!isPathInside(rootWithSep, realPath)) throw new FsSafeError("outside-workspace", "file is outside workspace root");
if (params.append !== true && params.truncateExisting !== false && !createdForWrite) await handle.truncate(0);
return {
handle,
createdForWrite,
realPath,
stat,
[Symbol.asyncDispose]: async () => {
await closeHandleForDispose(handle);
}
};
} catch (err) {
const cleanupCreatedPath = createdForWrite && err instanceof FsSafeError;
const cleanupPath = realPathForCleanup ?? ioPath;
await handle.close().catch(() => {});
if (cleanupCreatedPath) await fs$1.rm(cleanupPath, { force: true }).catch(() => {});
throw err;
}
}
async function appendFileInRoot(root, params) {
const target = await openWritableFileInRoot(root, {
relativePath: params.relativePath,
mkdir: params.mkdir,
mode: params.mode,
denyMutations: params.denyMutations,
truncateExisting: false,
append: true
});
try {
let prefix = "";
if (params.prependNewlineIfNeeded === true && !target.createdForWrite && target.stat.size > 0 && (typeof params.data === "string" && !params.data.startsWith("\n") || Buffer.isBuffer(params.data) && params.data.length > 0 && params.data[0] !== 10)) {
const lastByte = Buffer.alloc(1);
const { bytesRead } = await target.handle.read(lastByte, 0, 1, target.stat.size - 1);
if (bytesRead === 1 && lastByte[0] !== 10) prefix = "\n";
}
if (typeof params.data === "string") {
await target.handle.appendFile(`${prefix}${params.data}`, params.encoding ?? "utf8");
return;
}
const payload = prefix.length > 0 ? Buffer.concat([Buffer.from(prefix, "utf8"), params.data]) : params.data;
await target.handle.appendFile(payload);
} finally {
await target.handle.close().catch(() => {});
}
}
async function removePathInRoot(root, params) {
const resolved = await resolvePinnedRemovePathInRoot(root, params.relativePath, params.denyMutations);
if (process.platform === "win32") {
await removePathFallback(resolved);
return;
}
try {
await runPinnedPathHelper({
operation: "remove",
rootPath: resolved.rootReal,
relativePath: resolved.relativePosix
});
} catch (error) {
if (isPinnedPathHelperSpawnError(error)) {
await removePathFallback(resolved);
return;
}
throw normalizePinnedPathError(error);
}
}
async function mkdirPathInRoot(root, params) {
const resolved = await resolvePinnedPathInRoot(root, params);
if (process.platform === "win32") {
await mkdirPathFallback(resolved);
return;
}
try {
await runPinnedPathHelper({
operation: "mkdirp",
rootPath: resolved.rootReal,
relativePath: resolved.relativePosix
});
} catch (error) {
if (isPinnedPathHelperSpawnError(error)) {
await mkdirPathFallback(resolved);
return;
}
throw normalizePinnedPathError(error);
}
}
async function writeFileInRoot(root, params) {
if (process.platform === "win32") {
await serializePathWrite(rootWriteQueueKey(root, params.relativePath), async () => {
await writeFileFallback(root, params);
});
return;
}
const pinned = await resolvePinnedWriteTargetInRoot(root, params.relativePath, params.mode, params.denyMutations);
await serializePathWrite(pinned.targetPath, async () => {
let identity;
try {
identity = await runPinnedWriteHelper({
rootPath: pinned.rootReal,
relativeParentPath: pinned.relativeParentPath,
basename: pinned.basename,
mkdir: params.mkdir !== false,
mode: params.mode ?? pinned.mode,
overwrite: params.overwrite,
input: {
kind: "buffer",
data: params.data,
encoding: params.encoding
}
});
} catch (error) {
if (params.overwrite === false && isAlreadyExistsError(error)) throw new FsSafeError("already-exists", "file already exists", { cause: error instanceof Error ? error : void 0 });
throw normalizePinnedWriteError(error);
}
try {
await verifyAtomicWriteResult({
root,
targetPath: pinned.targetPath,
expectedIdentity: identity
});
} catch (err) {
emitWriteBoundaryWarning(`post-write verification failed: ${String(err)}`);
throw err;
}
});
}
async function copyFileInRoot(root, params) {
assertValidRootRelativePath(params.relativePath);
assertNoNulPathInput(params.sourcePath, "source path contains a NUL byte");
const source = await openVerifiedLocalFile(params.sourcePath, { hardlinks: params.sourceHardlinks });
if (params.maxBytes !== void 0 && source.stat.size > params.maxBytes) {
await source.handle.close().catch(() => {});
throw new FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${source.stat.size})`);
}
try {
if (process.platform === "win32") {
await serializePathWrite(rootWriteQueueKey(root, params.relativePath), async () => {
await copyFileFallback(root, params, source);
});
return;
}
const pinned = await resolvePinnedWriteTargetInRoot(root, params.relativePath, params.mode, params.denyMutations);
await serializePathWrite(pinned.targetPath, async () => {
let identity;
try {
if (getFsSafePythonConfig().mode === "off") {
await copyFileFallback(root, params, source);
return;
}
identity = await runPinnedCopyHelper({
rootPath: pinned.rootReal,
relativeParentPath: pinned.relativeParentPath,
basename: pinned.basename,
mkdir: params.mkdir !== false,
mode: pinned.mode,
overwrite: true,
maxBytes: params.maxBytes,
sourcePath: source.realPath,
sourceIdentity: {
dev: source.stat.dev,
ino: source.stat.ino
}
});
} catch (error) {
if (canFallbackFromPythonError(error)) {
await copyFileFallback(root, params, source);
return;
}
throw normalizePinnedWriteError(error);
}
try {
await verifyAtomicWriteResult({
root,
targetPath: pinned.targetPath,
expectedIdentity: identity
});
} catch (err) {
emitWriteBoundaryWarning(`post-copy verification failed: ${String(err)}`);
throw err;
}
});
} finally {
await source.handle.close().catch(() => {});
}
}
async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode, denyMutations) {
const { rootReal, rootWithSep, resolved } = await resolvePathInRoot(root, relativePath);
await assertMutationNotDenied(resolved, denyMutations);
try {
await assertNoPathAliasEscape({
absolutePath: resolved,
rootPath: rootReal,
boundaryLabel: "root"
});
} catch (err) {
throw new FsSafeError("path-alias", "path alias escape blocked", { cause: err });
}
const relativeResolved = path.relative(rootReal, resolved);
if (path.isAbsolute(relativeResolved)) throw new FsSafeError("outside-workspace", "file is outside workspace root");
const relativePosix = relativeResolved ? relativeResolved.split(path.sep).join(path.posix.sep) : "";
const basename = path.posix.basename(relativePosix);
if (!basename || basename === "." || basename === "/") throw new FsSafeError("invalid-path", "invalid target path");
let mode = requestedMode ?? 384;
try {
const opened = await openFileInRoot(root, {
relativePath,
hardlinks: "reject",
nonBlockingRead: true
});
try {
mode = requestedMode ?? opened.stat.mode & 511;
if (!isPathInside(rootWithSep, opened.realPath)) throw new FsSafeError("outside-workspace", "file is outside workspace root");
} finally {
await opened.handle.close().catch(() => {});
}
} catch (err) {
if (!(err instanceof FsSafeError) || err.code !== "not-found") throw err;
}
return {
rootReal,
targetPath: resolved,
relativeParentPath: path.posix.dirname(relativePosix) === "." ? "" : path.posix.dirname(relativePosix),
basename,
mode: mode || 384
};
}
async function resolvePinnedPathInRoot(root, params) {
return await resolvePinnedOperationPathInRoot(root, {
allowRoot: params.allowRoot,
denyMutations: params.denyMutations,
protectDenyMutationAncestors: false,
relativePath: params.relativePath,
policy: PATH_ALIAS_POLICIES.strict
});
}
async function resolvePinnedRemovePathInRoot(root, relativePath, denyMutations) {
return await resolvePinnedOperationPathInRoot(root, {
denyMutations,
protectDenyMutationAncestors: true,
relativePath,
policy: PATH_ALIAS_POLICIES.unlinkTarget
});
}
async function resolvePinnedOperationPathInRoot(root, params) {
const resolved = await resolvePinnedRootPathInRoot(root, {
relativePath: params.relativePath,
policy: params.policy
});
const relativeResolved = path.relative(resolved.rootReal, resolved.canonicalPath);
if ((relativeResolved === "" || relativeResolved === ".") && params.allowRoot === true) {
await assertMutationNotDenied(resolved.canonicalPath, params.denyMutations);
return {
rootReal: resolved.rootReal,
resolved: resolved.canonicalPath,
relativePosix: ""
};
}
const firstSegment = relativeResolved.split(path.sep)[0];
if (relativeResolved === "" || relativeResolved === "." || firstSegment === ".." || path.isAbsolute(relativeResolved)) throw new FsSafeError("outside-workspace", "file is outside workspace root");
const relativePosix = relativeResolved.split(path.sep).join(path.posix.sep);
if (!isPathInside(resolved.rootWithSep, resolved.canonicalPath)) throw new FsSafeError("outside-workspace", "file is outside workspace root");
await assertMutationNotDenied(resolved.canonicalPath, params.denyMutations, { protectAncestors: params.protectDenyMutationAncestors });
return {
rootReal: resolved.rootReal,
resolved: resolved.canonicalPath,
relativePosix
};
}
async function resolvePinnedRootPathInRoot(root, params) {
const rootReal = root.rootReal;
let resolved;
try {
resolved = await resolveRootPath({
absolutePath: path.resolve(rootReal, await expandRelativePathWithHome(params.relativePath)),
rootPath: rootReal,
rootCanonicalPath: rootReal,
boundaryLabel: "root",
policy: params.policy
});
} catch (err) {
throw new FsSafeError("path-alias", "path alias escape blocked", { cause: err });
}
const rootWithSep = ensureTrailingSep(resolved.rootCanonicalPath);
return {
rootReal: resolved.rootCanonicalPath,
rootWithSep,
canonicalPath: resolved.canonicalPath
};
}
async function removePathFallback(resolved) {
const guard = await createAsyncDirectoryGuard(path.dirname(resolved.resolved));
await void 0;
await assertAsyncDirectoryGuard(guard);
await ((await fs$1.lstat(resolved.resolved)).isDirectory() ? fs$1.rmdir(resolved.resolved) : fs$1.rm(resolved.resolved));
await assertAsyncDirectoryGuard(guard).catch(() => void 0);
}
async function mkdirPathFallback(resolved) {
await mkdirPathComponentsWithGuards({
rootReal: resolved.rootReal,
targetPath: resolved.resolved,
beforeComponent: async (componentPath) => await void 0
});
}
async function statPathFallback(root, relativePath) {
const resolved = await resolvePinnedPathInRoot(root, {
relativePath,
allowRoot: true
});
try {
return pathStatFromStats(await fs$1.lstat(resolved.resolved));
} catch (error) {
if (isNotFoundPathError(error)) throw new FsSafeError("not-found", "file not found", { cause: error instanceof Error ? error : void 0 });
throw error;
}
}
async function listPathFallback(root, relativePath, withFileTypes) {
const resolved = await resolvePinnedPathInRoot(root, {
relativePath,
allowRoot: true
});
try {
const sortedNames = (await fs$1.readdir(resolved.resolved)).toSorted();
if (!withFileTypes) return sortedNames;
const entries = [];
for (const name of sortedNames) entries.push({
name,
...pathStatFromStats(await fs$1.lstat(path.join(resolved.resolved, name)))
});
return entries;
} catch (error) {
if (isNotFoundPathError(error)) throw new FsSafeError("not-found", "directory not found", { cause: error instanceof Error ? error : void 0 });
throw error;
}
}
async function assertMoveMutationAllowed(root, params) {
await assertMutationNotDenied((await resolvePathInRoot(root, params.fromRelative)).resolved, params.denyMutations, { protectAncestors: true });
await assertMutationNotDenied((await resolvePathInRoot(root, params.toRelative)).resolved, params.denyMutations, { protectAncestors: true });
}
async function movePathFallback(root, params) {
const source = await resolvePathInRoot(root, params.fromRelative);
await assertMutationNotDenied(source.resolved, params.denyMutations, { protectAncestors: true });
await resolvePinnedRootPathInRoot(root, {
relativePath: params.fromRelative,
policy: PATH_ALIAS_POLICIES.strict
});
const target = await resolvePathInRoot(root, params.toRelative);
await assertMutationNotDenied(target.resolved, params.denyMutations, { protectAncestors: true });
await resolvePinnedRootPathInRoot(root, {
relativePath: params.toRelative,
policy: PATH_ALIAS_POLICIES.unlinkTarget
});
try {
await assertNoPathAliasEscape({
absolutePath: target.resolved,
rootPath: target.rootReal,
boundaryLabel: "root"
});
} catch (error) {
throw new FsSafeError("path-alias", "path alias escape blocked", { cause: error instanceof Error ? error : void 0 });
}
let sourceStat;
try {
sourceStat = await fs$1.lstat(source.resolved);
} catch (error) {
if (isNotFoundPathError(error)) throw new FsSafeError("not-found", "file not found", { cause: error instanceof Error ? error : void 0 });
throw error;
}
if (sourceStat.isSymbolicLink()) throw new FsSafeError("symlink", "symlink not allowed");
if (sourceStat.isFile() && sourceStat.nlink > 1) throw new FsSafeError("hardlink", "hardlinked path not allowed");
if (!params.overwrite && sourceStat.isDirectory()) throw new FsSafeError("invalid-path", "directory moves require overwrite: true");
if (!params.overwrite) try {
await fs$1.lstat(target.resolved);
throw new FsSafeError("already-exists", "destination exists");
} catch (error) {
if (error instanceof FsSafeError) throw error;
if (!isNotFoundPathError(error)) throw error;
}
const sourceParentGuard = await createAsyncDirectoryGuard(path.dirname(source.resolved));
const targetParentGuard = await createNearestExistingDirectoryGuard(target.rootReal, path.dirname(target.resolved));
await void 0;
await assertAsyncDirectoryGuard(sourceParentGuard);
await assertAsyncDirectoryGuard(targetParentGuard);
try {
await fs$1.rename(source.resolved, target.resolved);
} catch (error) {
if (isNotFoundPathError(error)) throw new FsSafeError("not-found", "file not found", { cause: error instanceof Error ? error : void 0 });
if (hasNodeErrorCode(error, "EEXIST")) throw new FsSafeError("already-exists", "destination exists", { cause: error instanceof Error ? error : void 0 });
throw error;
}
await assertAsyncDirectoryGuard(targetParentGuard).catch(() => void 0);
}
async function writeFileFallback(root, params) {
if (params.overwrite === false) {
await writeMissingFileFallback(root, params);
return;
}
const target = await openWritableFileInRoot(root, {
relativePath: params.relativePath,
mkdir: params.mkdir,
mode: params.mode,
denyMutations: params.denyMutations,
truncateExisting: false
});
const destinationPath = target.realPath;
const mode = params.mode ?? target.stat.mode & 511;
await target.handle.close().catch(() => {});
const destinationGuard = await createAsyncDirectoryGuard(path.dirname(destinationPath));
let tempPath = null;
let unregisterTempPath = null;
try {
tempPath = buildAtomicWriteTempPath(destinationPath);
unregisterTempPath = registerTempPathForExit(tempPath);
const writtenStat = await writeTempFileForAtomicReplace({
tempPath,
data: params.data,
encoding: params.encoding,
mode: mode || 384
});
const commitTempPath = tempPath;
await withAsyncDirectoryGuards([destinationGuard], async () => {
await fs$1.rename(commitTempPath, destinationPath);
});
tempPath = null;
unregisterTempPath();
unregisterTempPath = null;
try {
await verifyAtomicWriteResult({
root,
targetPath: destinationPath,
expectedIdentity: writtenStat
});
} catch (err) {
emitWriteBoundaryWarning(`post-write verification failed: ${String(err)}`);
throw err;
}
} finally {
if (tempPath) await fs$1.rm(tempPath, { force: true }).catch(() => {});
unregisterTempPath?.();
}
}
async function writeMissingFileFallback(root, params) {
const { rootReal, resolved } = await resolvePathInRoot(root, params.relativePath);
await assertMutationNotDenied(resolved, params.denyMutations);
try {
await assertNoPathAliasEscape({
absolutePath: resolved,
rootPath: rootReal,
boundaryLabel: "root"
});
} catch (err) {
throw new FsSafeError("path-alias", "path alias escape blocked", { cause: err });
}
if (params.mkdir !== false) await fs$1.mkdir(path.dirname(resolved), { recursive: true });
const parentGuard = await createAsyncDirectoryGuard(path.dirname(resolved));
let created = false;
try {
const { handle, writtenStat } = await withAsyncDirectoryGuards([parentGuard], async () => {
const handle = await fs$1.open(resolved, OPEN_WRITE_CREATE_FLAGS, params.mode ?? 384);
created = true;
try {
if (typeof params.data === "string") await handle.writeFile(params.data, params.encoding ?? "utf8");
else await handle.writeFile(params.data);
return {
handle,
writtenStat: await handle.stat()
};
} catch (error) {
await handle.close().catch(() => void 0);
throw error;
}
}, { onPostGuardFailure: async ({ handle }) => {
created = false;
await handle.close().catch(() => void 0);
} });
await handle.close();
await verifyAtomicWriteResult({
root,
targetPath: resolved,
expectedIdentity: writtenStat
});
created = false;
} catch (err) {
if (hasNodeErrorCode(err, "EEXIST")) throw new FsSafeError("already-exists", "file already exists", { cause: err instanceof Error ? err : void 0 });
throw err;
} finally {
if (created) await fs$1.rm(resolved, { force: true }).catch(() => void 0);
}
}
async function copyFileFallback(root, params, source) {
let target = null;
let sourceClosedByStream = false;
let targetClosedByUs = false;
let tempHandle = null;
let tempPath = null;
let unregisterTempPath = null;
let tempClosedByStream = false;
try {
target = await openWritableFileInRoot(root, {
relativePath: params.relativePath,
mkdir: params.mkdir,
mode: params.mode,
denyMutations: params.denyMutations,
truncateExisting: false
});
const destinationPath = target.realPath;
const mode = params.mode ?? target.stat.mode & 511;
await target.handle.close().catch(() => {});
targetClosedByUs = true;
const destinationGuard = await createAsyncDirectoryGuard(path.dirname(destinationPath));
tempPath = buildAtomicWriteTempPath(destinationPath);
unregisterTempPath = registerTempPathForExit(tempPath);
tempHandle = await fs$1.open(tempPath, OPEN_WRITE_CREATE_FLAGS, mode || 384);
const sourceStream = createBoundedReadStream(source, params.maxBytes);
const targetStream = tempHandle.createWriteStream();
sourceStream.once("close", () => {
sourceClosedByStream = true;
});
targetStream.once("close", () => {
tempClosedByStream = true;
});
await pipeline(sourceStream, targetStream);
const writtenStat = await fs$1.stat(tempPath);
if (!tempClosedByStream) {
await tempHandle.close().catch(() => {});
tempClosedByStream = true;
}
tempHandle = null;
const commitTempPath = tempPath;
await withAsyncDirectoryGuards([destinationGuard], async () => {
await fs$1.rename(commitTempPath, destinationPath);
});
tempPath = null;
unregisterTempPath();
unregisterTempPath = null;
try {
await verifyAtomicWriteResult({
root,
targetPath: destinationPath,
expectedIdentity: writtenStat
});
} catch (err) {
emitWriteBoundaryWarning(`post-copy verification failed: ${String(err)}`);
throw err;
}
} catch (err) {
if (target?.createdForWrite) await fs$1.rm(target.realPath, { force: true }).catch(() => {});
throw err;
} finally {
if (!sourceClosedByStream) await source.handle.close().catch(() => {});
if (tempHandle && !tempClosedByStream) await tempHandle.close().catch(() => {});
if (tempPath) await fs$1.rm(tempPath, { force: true }).catch(() => {});
unregisterTempPath?.();
if (target && !targetClosedByUs) await target.handle.close().catch(() => {});
}
}
//#endregion
//#region node_modules/@openclaw/fs-safe/dist/secure-temp-dir.js
function isNodeErrorWithCode(err, code) {
return typeof err === "object" && err !== null && "code" in err && err.code === code;
}
function resolveSecureTempRoot(options) {
const TMP_DIR_ACCESS_MODE = fs.constants.W_OK | fs.constants.X_OK;
const accessSync = options.accessSync ?? fs.accessSync;
const chmodSync = options.chmodSync ?? fs.chmodSync;
const lstatSync = options.lstatSync ?? fs.lstatSync;
const mkdirSync = options.mkdirSync ?? fs.mkdirSync;
const warn = options.warn ?? ((message) => console.warn(message));
const warningPrefix = options.warningPrefix ?? "[fs-safe]";
const unsafeFallbackLabel = options.unsafeFallbackLabel ?? "secure temp dir";
const getuid = options.getuid ?? (() => {
try {
return typeof process.getuid === "function" ? process.getuid() : void 0;
} catch {
return;
}
});
const tmpdir$1 = typeof options.tmpdir === "function" ? options.tmpdir : tmpdir;
const platform = options.platform ?? process.platform;
const uid = getuid();
const isSecureDirForUser = (st) => {
if (uid === void 0) return true;
if (typeof st.uid === "number" && st.uid !== uid) return false;
if (typeof st.mode === "number" && (st.mode & 18) !== 0) return false;
return true;
};
const fallback = () => {
const base = tmpdir$1();
const suffix = uid === void 0 ? options.fallbackPrefix : `${options.fallbackPrefix}-${uid}`;
return (platform === "win32" ? path.win32.join : path.join)(base, suffix);
};
const isTrustedTmpDir = (st) => {
return st.isDirectory() && !st.isSymbolicLink() && isSecureDirForUser(st);
};
const resolveDirState = (candidatePath) => {
try {
if (!isTrustedTmpDir(lstatSync(candidatePath))) return "invalid";
accessSync(candidatePath, TMP_DIR_ACCESS_MODE);
return "available";
} catch (err) {
if (isNodeErrorWithCode(err, "ENOENT")) return "missing";
return "invalid";
}
};
const tryRepairWritableBits = (candidatePath) => {
try {
const st = lstatSync(candidatePath);
if (!st.isDirectory() || st.isSymbolicLink()) return false;
if (uid !== void 0 && typeof st.uid === "number" && st.uid !== uid) return false;
if (typeof st.mode !== "number") return false;
if ((st.mode & 18) === 0) return resolveDirState(candidatePath) === "available";
try {
chmodSync(candidatePath, 448);
} catch (chmodErr) {
if (isNodeErrorWithCode(chmodErr, "EPERM") || isNodeErrorWithCode(chmodErr, "EACCES") || isNodeErrorWithCode(chmodErr, "ENOENT")) return resolveDirState(candidatePath) === "available";
throw chmodErr;
}
warn(`${warningPrefix} tightened permissions on temp dir: ${candidatePath}`);
return resolveDirState(candidatePath) === "available";
} catch {
return false;
}
};
const ensureTrustedFallbackDir = () => {
const fallbackPath = fallback();
const state = resolveDirState(fallbackPath);
if (state === "available") return fallbackPath;
if (state === "invalid") {
if (tryRepairWritableBits(fallbackPath)) return fallbackPath;
throw new Error(`Unsafe fallback ${unsafeFallbackLabel}: ${fallbackPath}`);
}
try {
mkdirSync(fallbackPath, {
recursive: true,
mode: 448
});
chmodSync(fallbackPath, 448);
} catch {
throw new Error(`Unable to create fallback ${unsafeFallbackLabel}: ${fallbackPath}`);
}
if (resolveDirState(fallbackPath) !== "available" && !tryRepairWritableBits(fallbackPath)) throw new Error(`Unsafe fallback ${unsafeFallbackLabel}: ${fallbackPath}`);
return fallbackPath;
};
if (options.skipPreferredOnWindows === true && platform === "win32") return ensureTrustedFallbackDir();
if (!options.preferredDir) return ensureTrustedFallbackDir();
const existingPreferredState = resolveDirState(options.preferredDir);
if (existingPreferredState === "available") return options.preferredDir;
if (existingPreferredState === "invalid") {
if (tryRepairWritableBits(options.preferredDir)) return options.preferredDir;
return ensureTrustedFallbackDir();
}
try {
accessSync(path.dirname(options.preferredDir), TMP_DIR_ACCESS_MODE);
mkdirSync(options.preferredDir, {
recursive: true,
mode: 448
});
chmodSync(options.preferredDir, 448);
if (resolveDirState(options.preferredDir) !== "available" && !tryRepairWritableBits(options.preferredDir)) return ensureTrustedFallbackDir();
return options.preferredDir;
} catch {
return ensureTrustedFallbackDir();
}
}
//#endregion
export { root as a, assertNoPathAliasEscape as c, expandHomePrefix as d, resolveHomeRelativePath as f, readLocalFileSafely as i, runPinnedWriteHelper as l, DEFAULT_ROOT_MAX_BYTES as n, getFsSafeTestHooks as o, resolveUserPath as p, openLocalFileSafely as r, PATH_ALIAS_POLICIES as s, resolveSecureTempRoot as t, resolveOpenedFileRealPathForHandle as u };