| title | Native helper policy |
|---|---|
| description | How fs-safe loads its platform-specific native filesystem primitives and how auto, require, and off affect guarded fallbacks. |
@openclaw/fs-safe declares seven exact-version optional packages for Linux
x64/arm64 (glibc or musl), macOS x64/arm64, and Windows x64. Package-manager
OS, CPU, and libc filters install only the matching package. The loader requires
that package lazily, without runtime downloads, postinstall scripts, or a
consumer Rust build.
import { configureFsSafeNative } from "@openclaw/fs-safe/config";
configureFsSafeNative({ mode: "auto" }); // default
configureFsSafeNative({ mode: "off" }); // guarded JavaScript only
configureFsSafeNative({ mode: "require" }); // fail closed when the binding is unavailableThe equivalent environment variables are FS_SAFE_NATIVE_MODE and OPENCLAW_FS_SAFE_NATIVE_MODE. Accepted values are auto, off, require, true, false, on, never, required, 1, and 0.
| Mode | Behavior |
|---|---|
auto |
Prefer native primitives when the current platform package loads; otherwise silently use the guarded JavaScript path. |
off |
Do not load a native package. Use the guarded JavaScript path deterministically. |
require |
Throw FsSafeError("helper-unavailable") instead of falling back when an operation needs the native binding and it cannot load. |
Configure the mode once during startup. Loading is lazy and cached; changing from auto to require after a failed load changes failure policy but does not repeatedly probe the binary.
tempWorkspace() and its scoped/sync variants
remain available in every mode. Their default compatible cleanup uses guarded
JavaScript quarantine when owned native tree removal is unavailable.
cleanupSafety: "require-bounded" instead rejects before child creation unless
no-replace quarantine plus descriptor-relative owned-tree removal are available.
On Linux, admission probes the exact openat2 child-directory flags, including
RESOLVE_NO_XDEV, at runtime. An unavailable or denied probe selects compatible
JavaScript cleanup even in global require mode; require-bounded rejects before
child creation.
Already-created strict workspaces retain their binding
and descriptors across later mode changes.
stageFileInDirectory() always requires native support on
Linux/macOS and rejects before creation when off, unavailable, or missing the
required capability. Windows is unsupported for this lifecycle. This does not
change the mode policy of existing fallback-capable APIs.
The native layer exposes policy-free filesystem mechanisms: beneath-root open/mkdir/link, replace and no-replace rename, identity reads, archive decode/execution, clone/copy/hash workers, and Windows security descriptor calls. The TypeScript layer owns policy, retries, filters, budgets, modes, cleanup, error normalization, and the decision to fall back.
- Linux uses
openat2withRESOLVE_BENEATH | RESOLVE_NO_MAGICLINKS,renameat, andrenameat2(RENAME_NOREPLACE). Owned-tree cleanup enumerates and unlinks through retained directory descriptors and rejects device crossings. - macOS 15.4 and newer prefer
O_RESOLVE_BENEATH; older kernels resolve components withO_NOFOLLOWand restart in-root symlinks from the pinned root descriptor. Both routes use anF_GETPATHpost-open escape detector and reportbest-effortbecause directory rename races are not atomic with that check. Publication usesrenameatfor replacement andrenameatx_np(RENAME_EXCL)for no-replace; owned-tree cleanup uses descriptor-relativeopenat/unlinkat. - Windows uses handle-relative
NtCreateFile, rejects reparse points during root-bounded traversal, usesFileRenameInfoExwith replacement selected explicitly by the TypeScript policy layer, and deletes owned trees through exact opened handles withFileDispositionInfoEx; symlink/reparse entries in owned trees are removed as leaves and never traversed.
Native primitives back create-only and replacing pinned writes, async sidecar creation, guarded publication, archive acceleration, and direct Windows ACL operations. Equivalent JavaScript paths remain available for documented fallback-capable features. See Native architecture for the exact difference.
The guarded JavaScript mutation path is detection-based, not containment-atomic.
If a same-privilege peer can replace a writable parent after its identity guard
but before Node resolves a pathname mutation, the mutation can land outside the
intended root before the post-operation guard throws. Select require rather
than auto or off when that concurrent attacker is part of the threat model.
openBeneath() returns { fd, containment }. containment is
"kernel-atomic" for Linux openat2 and "best-effort" for macOS and
Windows. Public JavaScript root open/read/writable results also expose the
field and report "best-effort"; the label reports mechanism, not policy.
Version 0.5 removes the Python worker and interpreter-path selection. The mode contract is unchanged, so migrate startup configuration directly:
| Python helper configuration | Native replacement |
|---|---|
configureFsSafePython({ mode: "auto" }) |
configureFsSafeNative({ mode: "auto" }) |
configureFsSafePython({ mode: "off" }) |
configureFsSafeNative({ mode: "off" }) |
configureFsSafePython({ mode: "require" }) |
configureFsSafeNative({ mode: "require" }) |
FS_SAFE_PYTHON_MODE |
FS_SAFE_NATIVE_MODE |
OPENCLAW_FS_SAFE_PYTHON_MODE |
OPENCLAW_FS_SAFE_NATIVE_MODE |
pythonPath, FS_SAFE_PYTHON, and the OpenClaw interpreter-path aliases |
Remove; prebuilt bindings do not use an interpreter path |
In 0.5, configureFsSafePython and the legacy Python environment names
remain only as an upgrade bridge. On the first config read they emit one
DeprecationWarning with code FS_SAFE_PYTHON_DEPRECATED, state the mapped
native mode, and then apply that mode. A legacy interpreter path without an
explicit mode maps to auto and the path itself is ignored. Native config has
the normal precedence over legacy environment config.
There is no silent alias and no Python execution fallback. The bridge exists only to make shipped 0.4 configuration visible and predictable while the consumer performs its 0.5 upgrade.