fs-safe/docs/install.md
2026-05-06 01:43:39 +01:00

5.7 KiB

Install

fs-safe is published to npm as @openclaw/fs-safe. It targets Node 20.11 or newer, ships ESM only, and works on macOS, Linux, and Windows.

Package managers

pnpm add @openclaw/fs-safe
npm install @openclaw/fs-safe
yarn add @openclaw/fs-safe
bun add @openclaw/fs-safe

Node version

Minimum Node 20.11. The package uses fs.promises, fs.constants.O_NOFOLLOW where available, and node:stream/promises. Earlier Node releases will fail at import time.

Verify the runtime:

node --version
# v20.11.0 or newer

TypeScript

Types ship with the package — no @types/openclaw__fs-safe needed. The exports map in package.json provides typed entries for every subpath:

import { root, FsSafeError } from "@openclaw/fs-safe";
import { writeJson } from "@openclaw/fs-safe/json";
import { extractArchive } from "@openclaw/fs-safe/archive";

A working tsconfig.json for consumers:

{
  "compilerOptions": {
    "target": "es2022",
    "module": "node18",
    "moduleResolution": "node16",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  }
}

Subpath exports

Use the main entry for the common surface, or the focused subpaths when you want a leaner import or to depend on a narrower contract:

Subpath Contents
@openclaw/fs-safe Small common surface: root, root types, and errors.
@openclaw/fs-safe/root root(), Root, RootDefaults, related types.
@openclaw/fs-safe/config Process-global Python helper configuration.
@openclaw/fs-safe/path isPathInside, safeRealpathSync, isWithinDir, error helpers.
@openclaw/fs-safe/json tryReadJson, readJson, readJsonIfExists, writeJson, sync variants.
@openclaw/fs-safe/store fileStore(), fileStoreSync(), and jsonStore<T>().
@openclaw/fs-safe/secret Secret file read/write helpers.
@openclaw/fs-safe/atomic replaceFileAtomic, writeTextAtomic, replaceDirectoryAtomic, movePathWithCopyFallback.
@openclaw/fs-safe/temp tempWorkspace, withTempWorkspace, sync variants, resolveSecureTempRoot.
@openclaw/fs-safe/secure-file readSecureFile for pinned absolute file reads with permissions checks.
@openclaw/fs-safe/file-lock acquireFileLock, withFileLock, createFileLockManager, and related lock types.
@openclaw/fs-safe/permissions POSIX mode and Windows ACL inspection/remediation helpers.
@openclaw/fs-safe/walk walkDirectory, walkDirectorySync, related types. Budget-bounded, not root-bounded.
@openclaw/fs-safe/archive extractArchive, resolveArchiveKind, limits, preflight helpers.
@openclaw/fs-safe/advanced Lower-level composition helpers: path scopes, root-file open, install paths, local-root readers, temp-file targets, sibling-temp writes, regular-file helpers, pathExists, withTimeout, and related advanced types. This surface is less stable than the focused public subpaths.
@openclaw/fs-safe/errors FsSafeError, FsSafeErrorCode.
@openclaw/fs-safe/types Shared types: DirEntry, PathStat, BasePathOptions, …
@openclaw/fs-safe/test-hooks Test-only hooks for injecting races. Active under NODE_ENV=test.

Runtime dependencies

@openclaw/fs-safe lists jszip and tar as optional dependencies for archive extraction. They are loaded lazily and only required when ZIP/TAR helpers run. Installs that omit optional dependencies can still import and use every non-archive subpath; archive calls fail with a clear missing-optional-dependency message.

There are no peer dependencies and no native build step.

Python helper policy

On POSIX, root() uses one persistent Python helper process for the fd-relative operations Node does not expose cleanly. The default is auto: use the helper when it starts, fall back to Node-only behavior when it is disabled or unavailable.

import { configureFsSafePython } from "@openclaw/fs-safe/config";

configureFsSafePython({ mode: "auto" });    // default
configureFsSafePython({ mode: "off" });     // never spawn Python
configureFsSafePython({ mode: "require" }); // fail closed if unavailable

Environment variables are read at runtime:

FS_SAFE_PYTHON_MODE=off      # auto | off | require
FS_SAFE_PYTHON=/usr/bin/python3

OpenClaw compatibility aliases are also accepted: OPENCLAW_FS_SAFE_PYTHON_MODE, OPENCLAW_FS_SAFE_PYTHON, OPENCLAW_PINNED_PYTHON, and OPENCLAW_PINNED_WRITE_PYTHON.

Disabling Python keeps the public API working, but downgrades POSIX mutation hardening from fd-relative syscalls to Node path operations guarded by lexical and canonical checks plus identity verification. Use require for security-sensitive deployments where that downgrade should be a startup/runtime failure instead of a fallback. The full tradeoff is documented in Python helper policy.

Verify the install

import { root, FsSafeError } from "@openclaw/fs-safe";
import os from "node:os";
import path from "node:path";

const dir = path.join(os.tmpdir(), "fs-safe-smoke");
await import("node:fs/promises").then((fs) => fs.mkdir(dir, { recursive: true }));

const fs = await root(dir);
await fs.write("hello.txt", "ok\n");
console.log(await fs.readText("hello.txt"));

try {
  await fs.write("../escape.txt", "x");
} catch (err) {
  if (err instanceof FsSafeError) console.log("blocked:", err.code);
}

If the script prints ok followed by blocked: outside-workspace, your install is healthy.

Next

  • Quickstart — write, read, atomic, temp.
  • Security model — what the boundary defends against.
  • Errors — the closed code union you'll be catching.