@openclaw/openshell-sandbox 2026.10.5-beta.4 → 2026.10.10-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/node_modules/@openclaw/fs-safe/CHANGELOG.md +148 -0
- package/node_modules/@openclaw/fs-safe/README.md +20 -4
- package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/advanced.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-merge.js +28 -3
- package/node_modules/@openclaw/fs-safe/dist/archive-parser.wasm +0 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-read.js +11 -5
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-loader.js +4 -2
- package/node_modules/@openclaw/fs-safe/dist/archive.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/atomic-io.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/bounded-read.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/bounded-read.js +9 -2
- package/node_modules/@openclaw/fs-safe/dist/clone-metadata.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/clone-metadata.js +2 -1
- package/node_modules/@openclaw/fs-safe/dist/config.d.ts +6 -0
- package/node_modules/@openclaw/fs-safe/dist/config.js +5 -0
- package/node_modules/@openclaw/fs-safe/dist/copy-file-input.d.ts +3 -1
- package/node_modules/@openclaw/fs-safe/dist/copy-file-input.js +8 -7
- package/node_modules/@openclaw/fs-safe/dist/copy-link.d.ts +26 -0
- package/node_modules/@openclaw/fs-safe/dist/copy-link.js +170 -0
- package/node_modules/@openclaw/fs-safe/dist/copy-metadata.d.ts +9 -0
- package/node_modules/@openclaw/fs-safe/dist/copy-metadata.js +39 -0
- package/node_modules/@openclaw/fs-safe/dist/copy-path-admission.js +11 -2
- package/node_modules/@openclaw/fs-safe/dist/copy-root-file-sync.d.ts +2 -4
- package/node_modules/@openclaw/fs-safe/dist/copy-root-file-sync.js +4 -2
- package/node_modules/@openclaw/fs-safe/dist/copy-tree-portable.js +3 -6
- package/node_modules/@openclaw/fs-safe/dist/copy.js +3 -3
- package/node_modules/@openclaw/fs-safe/dist/create.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/creation-boundary.js +7 -4
- package/node_modules/@openclaw/fs-safe/dist/darwin-acl.d.ts +12 -0
- package/node_modules/@openclaw/fs-safe/dist/darwin-acl.js +29 -0
- package/node_modules/@openclaw/fs-safe/dist/directory-durability.d.ts +7 -4
- package/node_modules/@openclaw/fs-safe/dist/directory-guard.js +20 -5
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.js +27 -2
- package/node_modules/@openclaw/fs-safe/dist/directory-receipt.d.ts +7 -7
- package/node_modules/@openclaw/fs-safe/dist/directory-receipt.js +9 -4
- package/node_modules/@openclaw/fs-safe/dist/durability.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/entry-publication.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/exact-identity.d.ts +17 -0
- package/node_modules/@openclaw/fs-safe/dist/exact-identity.js +29 -0
- package/node_modules/@openclaw/fs-safe/dist/file-handle-transfer.js +4 -2
- package/node_modules/@openclaw/fs-safe/dist/file-hash.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/file-hash.js +11 -9
- package/node_modules/@openclaw/fs-safe/dist/file-identity.d.ts +7 -1
- package/node_modules/@openclaw/fs-safe/dist/file-identity.js +27 -5
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-admission.js +1 -2
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync.js +8 -1
- package/node_modules/@openclaw/fs-safe/dist/file-lock.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/file-lock.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/file-write-lease.d.ts +9 -0
- package/node_modules/@openclaw/fs-safe/dist/file-write-lease.js +42 -0
- package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.js +22 -15
- package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +3 -0
- package/node_modules/@openclaw/fs-safe/dist/hardlink-fallback.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/hardlink-fallback.js +5 -0
- package/node_modules/@openclaw/fs-safe/dist/index.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/index.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-ownership.js +16 -3
- package/node_modules/@openclaw/fs-safe/dist/local-file-descriptor.js +24 -13
- package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +85 -18
- package/node_modules/@openclaw/fs-safe/dist/native-config.d.ts +5 -0
- package/node_modules/@openclaw/fs-safe/dist/native-config.js +11 -0
- package/node_modules/@openclaw/fs-safe/dist/native-directory-observation.js +6 -4
- package/node_modules/@openclaw/fs-safe/dist/native-fallback-warning.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/native-fallback-warning.js +26 -2
- package/node_modules/@openclaw/fs-safe/dist/native-guidance.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/native-guidance.js +21 -0
- package/node_modules/@openclaw/fs-safe/dist/native-move-fallback.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/native-move-fallback.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/native-operations.js +8 -6
- package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.d.ts +44 -4
- package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.js +85 -15
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write-windows.d.ts +3 -3
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write-windows.js +33 -6
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.d.ts +4 -1
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +22 -3
- package/node_modules/@openclaw/fs-safe/dist/native-policy-directory-observation.js +4 -3
- package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.js +3 -0
- package/node_modules/@openclaw/fs-safe/dist/native-staged-file-metadata.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/native-staged-file-metadata.js +16 -0
- package/node_modules/@openclaw/fs-safe/dist/native-staged-file.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/native-staged-file.js +15 -5
- package/node_modules/@openclaw/fs-safe/dist/native-stat-identity.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/native-stat-identity.js +7 -0
- package/node_modules/@openclaw/fs-safe/dist/native-status.d.ts +17 -0
- package/node_modules/@openclaw/fs-safe/dist/native-status.js +30 -0
- package/node_modules/@openclaw/fs-safe/dist/native.d.ts +31 -4
- package/node_modules/@openclaw/fs-safe/dist/native.js +107 -9
- package/node_modules/@openclaw/fs-safe/dist/opened-realpath.js +9 -1
- package/node_modules/@openclaw/fs-safe/dist/overwrite-file-handle.js +4 -1
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/permission-exec.js +9 -1
- package/node_modules/@openclaw/fs-safe/dist/permissions-public.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/permissions-public.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/permissions-windows.js +83 -47
- package/node_modules/@openclaw/fs-safe/dist/pinned-open.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/pinned-open.js +8 -3
- package/node_modules/@openclaw/fs-safe/dist/pinned-write-input.js +13 -0
- package/node_modules/@openclaw/fs-safe/dist/pinned-write-staged.js +10 -8
- package/node_modules/@openclaw/fs-safe/dist/pinned-write-types.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/pinned-write.js +7 -5
- package/node_modules/@openclaw/fs-safe/dist/pipe.d.ts +9 -0
- package/node_modules/@openclaw/fs-safe/dist/pipe.js +26 -0
- package/node_modules/@openclaw/fs-safe/dist/private-directory.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/private-producer-handoff.js +2 -5
- package/node_modules/@openclaw/fs-safe/dist/private-temp-workspace.d.ts +3 -2
- package/node_modules/@openclaw/fs-safe/dist/private-temp-workspace.js +38 -10
- package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +92 -10
- package/node_modules/@openclaw/fs-safe/dist/publish-file-failure.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/publish-file-failure.js +3 -0
- package/node_modules/@openclaw/fs-safe/dist/publish-file.d.ts +5 -3
- package/node_modules/@openclaw/fs-safe/dist/publish-file.js +26 -20
- package/node_modules/@openclaw/fs-safe/dist/read-opened-file.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/read-opened-file.js +3 -4
- package/node_modules/@openclaw/fs-safe/dist/realpath.js +32 -0
- package/node_modules/@openclaw/fs-safe/dist/regular-file.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/regular-file.js +11 -15
- package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +3 -2
- package/node_modules/@openclaw/fs-safe/dist/root-copy.d.ts +18 -0
- package/node_modules/@openclaw/fs-safe/dist/root-copy.js +128 -0
- package/node_modules/@openclaw/fs-safe/dist/root-create-native.d.ts +8 -13
- package/node_modules/@openclaw/fs-safe/dist/root-create-native.js +252 -27
- package/node_modules/@openclaw/fs-safe/dist/root-directory-creation.js +5 -1
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.d.ts +11 -8
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +35 -13
- package/node_modules/@openclaw/fs-safe/dist/root-file-open-batch-sync.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/root-file-open-batch-sync.js +108 -0
- package/node_modules/@openclaw/fs-safe/dist/root-file.d.ts +7 -0
- package/node_modules/@openclaw/fs-safe/dist/root-file.js +15 -0
- package/node_modules/@openclaw/fs-safe/dist/root-impl.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/root-impl.js +154 -175
- package/node_modules/@openclaw/fs-safe/dist/root-mkdir-native.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/root-mkdir-native.js +239 -0
- package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +37 -15
- package/node_modules/@openclaw/fs-safe/dist/root-native-creation-path.d.ts +32 -0
- package/node_modules/@openclaw/fs-safe/dist/root-native-creation-path.js +56 -0
- package/node_modules/@openclaw/fs-safe/dist/root-options.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/root-public-types.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/root-remove-native.js +95 -16
- package/node_modules/@openclaw/fs-safe/dist/root-remove.js +6 -4
- package/node_modules/@openclaw/fs-safe/dist/root-walk.js +23 -19
- package/node_modules/@openclaw/fs-safe/dist/root-write-admission.js +8 -6
- package/node_modules/@openclaw/fs-safe/dist/root-write-publication.js +5 -1
- package/node_modules/@openclaw/fs-safe/dist/secure-file-windows.js +3 -1
- package/node_modules/@openclaw/fs-safe/dist/secure-file.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/secure-file.js +10 -4
- package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-retained.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-target.js +8 -1
- package/node_modules/@openclaw/fs-safe/dist/staged-directory.d.ts +13 -0
- package/node_modules/@openclaw/fs-safe/dist/staged-directory.js +29 -0
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.js +5 -3
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-descriptor.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-descriptor.js +13 -2
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-owner.js +71 -3
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.js +22 -0
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-types.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/test-hooks.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/test-hooks.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/trash.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/types.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-native.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/windows-appcontainer.d.ts +5 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-appcontainer.js +39 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-owner.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-owner.js +25 -36
- package/node_modules/@openclaw/fs-safe/dist/windows-security-command.js +24 -5
- package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.js +4 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-security-helper.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-security-helper.exe +0 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-security-helper.js +7 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-test-hooks.d.ts +16 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-test-hooks.js +51 -0
- package/node_modules/@openclaw/fs-safe/dist/write-file-handle.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/write-file-handle.js +12 -10
- package/node_modules/@openclaw/fs-safe/docs/advanced.md +2 -1
- package/node_modules/@openclaw/fs-safe/docs/archive.md +16 -0
- package/node_modules/@openclaw/fs-safe/docs/config.md +71 -1
- package/node_modules/@openclaw/fs-safe/docs/contributing.md +45 -5
- package/node_modules/@openclaw/fs-safe/docs/copy.md +45 -5
- package/node_modules/@openclaw/fs-safe/docs/durability.md +34 -7
- package/node_modules/@openclaw/fs-safe/docs/errors.md +8 -0
- package/node_modules/@openclaw/fs-safe/docs/guest.md +2 -0
- package/node_modules/@openclaw/fs-safe/docs/install.md +47 -27
- package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.5.md +6 -6
- package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.6.md +1 -1
- package/node_modules/@openclaw/fs-safe/docs/native-helper.md +79 -6
- package/node_modules/@openclaw/fs-safe/docs/native.md +279 -8
- package/node_modules/@openclaw/fs-safe/docs/permissions.md +64 -15
- package/node_modules/@openclaw/fs-safe/docs/pipe.md +81 -0
- package/node_modules/@openclaw/fs-safe/docs/platform-capabilities.md +63 -0
- package/node_modules/@openclaw/fs-safe/docs/public-api.md +10 -0
- package/node_modules/@openclaw/fs-safe/docs/queue-link-free-design.md +237 -0
- package/node_modules/@openclaw/fs-safe/docs/quickstart.md +23 -3
- package/node_modules/@openclaw/fs-safe/docs/reading.md +56 -2
- package/node_modules/@openclaw/fs-safe/docs/regular-file.md +2 -0
- package/node_modules/@openclaw/fs-safe/docs/secret-file.md +5 -5
- package/node_modules/@openclaw/fs-safe/docs/secure-file.md +1 -1
- package/node_modules/@openclaw/fs-safe/docs/security-model.md +35 -0
- package/node_modules/@openclaw/fs-safe/docs/sidecar-lock.md +31 -13
- package/node_modules/@openclaw/fs-safe/docs/staged-file.md +18 -0
- package/node_modules/@openclaw/fs-safe/docs/store.md +15 -0
- package/node_modules/@openclaw/fs-safe/docs/temp.md +39 -0
- package/node_modules/@openclaw/fs-safe/docs/testing.md +13 -0
- package/node_modules/@openclaw/fs-safe/docs/types.md +51 -81
- package/node_modules/@openclaw/fs-safe/docs/walk.md +4 -1
- package/node_modules/@openclaw/fs-safe/docs/watch.md +3 -2
- package/node_modules/@openclaw/fs-safe/docs/writing.md +121 -14
- package/node_modules/@openclaw/fs-safe/package.json +21 -12
- package/node_modules/@openclaw/fs-safe-darwin-arm64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-darwin-arm64/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-darwin-x64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-darwin-x64/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-freebsd-arm64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-freebsd-arm64/package.json +29 -0
- package/node_modules/@openclaw/fs-safe-freebsd-x64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-freebsd-x64/package.json +29 -0
- package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-arm64-musl/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-arm64-musl/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-x64-gnu/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-x64-gnu/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-x64-musl/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-x64-musl/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-win32-arm64-msvc/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-win32-arm64-msvc/package.json +29 -0
- package/node_modules/@openclaw/fs-safe-win32-x64-msvc/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-win32-x64-msvc/package.json +1 -1
- package/package.json +5 -5
- package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.cs +0 -336
- package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.ps1 +0 -90
|
@@ -5,6 +5,17 @@ description: "The platform-specific native bindings, fd-relative beneath model,
|
|
|
5
5
|
|
|
6
6
|
# Native architecture
|
|
7
7
|
|
|
8
|
+
Windows native realpath calls and handle-bound canonical-name checks share one
|
|
9
|
+
native resolver. It keeps DOS-name resolution first and uses normalized NT names
|
|
10
|
+
on the same handle only after `ERROR_ACCESS_DENIED` in an AppContainer. Ordinary
|
|
11
|
+
process errors retain their original code and identity. AppContainer support
|
|
12
|
+
requires the addon; native-off or unavailable membership leaves Node EPERM
|
|
13
|
+
unchanged without a subprocess probe. Drive-root translation is
|
|
14
|
+
uncached and fail-closed; see the [AppContainer limits](permissions.md#advanced-windows-acl-helpers).
|
|
15
|
+
|
|
16
|
+
See [platform filesystem capabilities](platform-capabilities.md) for Linux
|
|
17
|
+
write leases, public Darwin ACL facts, and Windows test fixtures.
|
|
18
|
+
|
|
8
19
|
`@openclaw/fs-safe` uses native bindings that supply mechanisms Node does
|
|
9
20
|
not expose directly. The Rust layer is deliberately not a second policy engine.
|
|
10
21
|
TypeScript owns trusted-root selection, path validation, archive filtering,
|
|
@@ -14,12 +25,46 @@ platform syscall sequence that can preserve the boundary.
|
|
|
14
25
|
|
|
15
26
|
Every operation that has an equivalent safe Node implementation keeps that
|
|
16
27
|
guarded JavaScript path. Native loading is lazy; installs do not compile Rust,
|
|
17
|
-
run postinstall code, or fetch binaries at runtime.
|
|
28
|
+
run postinstall code, or fetch binaries at runtime. Exact-version optional
|
|
18
29
|
packages are filtered by OS, CPU, and Linux libc, so an installation receives
|
|
19
|
-
only its matching prebuilt binding.
|
|
30
|
+
only its matching prebuilt binding. Windows ships both x64 and ARM64 MSVC
|
|
31
|
+
bindings; selection follows the Node process architecture. An x64 Node process
|
|
32
|
+
on Windows ARM64 uses the x64 binding under emulation.
|
|
20
33
|
Native-only operations fail explicitly
|
|
21
34
|
instead of substituting a weaker implementation.
|
|
22
35
|
|
|
36
|
+
## FreeBSD 14.4 and newer
|
|
37
|
+
|
|
38
|
+
The FreeBSD x64 and arm64 packages provide native anonymous pipes, descriptor
|
|
39
|
+
close and POSIX canonicalization. The larger Linux/macOS/Windows filesystem backend is excluded
|
|
40
|
+
from these binaries: its Unix gates contain platform-specific resolver, clone,
|
|
41
|
+
copy, observation and cleanup implementations. FreeBSD does not export stubs for
|
|
42
|
+
those operations. In `auto`, each operation selects its existing guarded JavaScript
|
|
43
|
+
or WASM backend; in `require`, a missing required capability fails with
|
|
44
|
+
`helper-unavailable`. Pure JavaScript APIs retain their ordinary behavior.
|
|
45
|
+
|
|
46
|
+
| Native capability | FreeBSD binding |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `createPipe`, `closeOwnedFd`, `canonicalizePath` | Implemented; real pipe, descriptor and pathname syscalls |
|
|
49
|
+
| Beneath open/create/mkdir/link, replace/no-replace rename and identity-fenced rename | Absent; guarded fallback where supported |
|
|
50
|
+
| Directory observations, retained files/symlinks, entry publication, owned-tree and root removal | Absent; guarded fallback or explicit native-only rejection |
|
|
51
|
+
| File/tree clone, native copy/hash, archive inspection/extraction and buffer readers | Absent; JavaScript/WASM fallback where supported |
|
|
52
|
+
| Native watch | Absent; polling fallback in `auto` |
|
|
53
|
+
| Darwin ACL and Windows security/identity methods | Not applicable |
|
|
54
|
+
|
|
55
|
+
The release workflow builds inside FreeBSD 14.4 VMs and checks installed-package
|
|
56
|
+
behavior on the main thread and a real Worker. ARM64 uses QEMU CPU emulation
|
|
57
|
+
with a real FreeBSD kernel. This is not a Linux cross-build runtime test. Build
|
|
58
|
+
locally on FreeBSD with `pnpm native:build:freebsd`; the JavaScript/WASM distribution
|
|
59
|
+
can be prepared separately with `pnpm build`. There is no qualified FreeBSD Bun
|
|
60
|
+
artifact in the runtime matrix. FreeBSD x64 installed-package proof covers npm
|
|
61
|
+
and pnpm. FreeBSD arm64 covers npm only because pnpm does not publish a native
|
|
62
|
+
binary for that target. CI prepares the ARM64 proof harness dependencies on Linux
|
|
63
|
+
and syncs them into the VM; consumer installations and runtime proof run inside
|
|
64
|
+
FreeBSD, without running pnpm there.
|
|
65
|
+
Reopening pipe descriptors through `/dev/fd` requires the standard `fdescfs`
|
|
66
|
+
mount there; the VM proof mounts it before testing reopen, inheritance and EOF.
|
|
67
|
+
|
|
23
68
|
## The beneath model
|
|
24
69
|
|
|
25
70
|
A trusted directory descriptor is the capability. Native operations accept
|
|
@@ -41,9 +86,23 @@ validated Root handle
|
|
|
41
86
|
The TypeScript layer validates and decides. The native layer never decides
|
|
42
87
|
whether a path, archive entry, mode, owner, or cleanup policy is acceptable.
|
|
43
88
|
|
|
89
|
+
Ordinary recursive `Root.mkdir()` calls retain the exact existing parent and
|
|
90
|
+
every opened missing component for the duration of one synchronous native walk.
|
|
91
|
+
Each creation is descriptor-relative, followed by fresh Root, retained-parent,
|
|
92
|
+
child identity and canonical-name checks before the next component. Explicit
|
|
93
|
+
mutation policies, callbacks, private permissions, aliases and bindings without
|
|
94
|
+
the required child-open capability retain their existing admission paths.
|
|
95
|
+
|
|
44
96
|
- Linux uses `openat2(RESOLVE_BENEATH | RESOLVE_NO_MAGICLINKS)`, fd-relative
|
|
45
97
|
`mkdirat`/`linkat`/`renameat`/`renameat2`, `FICLONE`, and `copy_file_range`.
|
|
46
98
|
Without `openat2`, beneath opens use the [guarded fallback](#linux-without-openat2).
|
|
99
|
+
Regular-file copies with `clone: "auto"` also recover when `FICLONE` returns
|
|
100
|
+
`EAGAIN` or `EIO`, as can happen for dirty extents on ZFS. Recovery requires
|
|
101
|
+
successful identity-checked removal of the unpublished exclusive clone target
|
|
102
|
+
before the native ordinary-copy path creates a new stage. `clone: "always"`
|
|
103
|
+
still fails when cloning fails. Ordinary-copy I/O errors, clone normalization
|
|
104
|
+
errors, and cleanup failures remain terminal; generic `EIO` is never a retry
|
|
105
|
+
signal.
|
|
47
106
|
- macOS 15.4 and newer first use `openat(O_RESOLVE_BENEATH)`; older kernels walk
|
|
48
107
|
components with `openat(O_NOFOLLOW)` and restart in-root symlinks from the
|
|
49
108
|
pinned root. Both routes apply an `F_GETPATH` post-open containment detector,
|
|
@@ -71,10 +130,21 @@ whether a path, archive entry, mode, owner, or cleanup policy is acceptable.
|
|
|
71
130
|
use `GetSecurityInfo`; private directories receive their protected DACL in
|
|
72
131
|
an exclusive, handle-relative `NtCreateFile` call. Their created handles remain
|
|
73
132
|
open through ACL and pathname-association checks and own any failure cleanup.
|
|
133
|
+
Admitted, resolved absolute paths use extended-length Win32 spelling for
|
|
134
|
+
pathname calls, including internal staging and public-name verification;
|
|
135
|
+
relative child names remain relative to retained handles. Long paths retain
|
|
136
|
+
the same alias validation, ACL checks, identity fences and cleanup ownership.
|
|
137
|
+
Already-admitted namespace spellings pass through unchanged, including
|
|
138
|
+
filesystem-drive paths using `\\.\`; the encoder does not admit or reject paths.
|
|
74
139
|
N-API descriptors cross into and out of
|
|
75
140
|
this layer only through the host executable's paired libuv descriptor bridge;
|
|
76
141
|
missing or partial exports fail with `ENOTSUP` instead of trying a raw HANDLE
|
|
77
142
|
or add-on CRT descriptor namespace.
|
|
143
|
+
`Root.copyIn` uses the same ReFS integrity, sparse-file, and cluster-aligned
|
|
144
|
+
block-clone operations as tree cloning, through retained source and destination
|
|
145
|
+
parent handles. The exclusive stage enters the existing guarded publication
|
|
146
|
+
path; transfer failures retain its cleanup receipt. It does not inherit the
|
|
147
|
+
tree API's source metadata policy.
|
|
78
148
|
|
|
79
149
|
The internal macOS `inspectDarwinAcl(fd)` capability reports `absent`, `empty`,
|
|
80
150
|
or `present` for the opened object's extended ACL. It synchronously borrows the
|
|
@@ -269,7 +339,8 @@ not bypass the byte limit.
|
|
|
269
339
|
## Mode semantics
|
|
270
340
|
|
|
271
341
|
See [Native helper policy](native-helper.md#modes) for the `auto`, `require`, and
|
|
272
|
-
`off` contracts and cached loader behavior.
|
|
342
|
+
`off` contracts and cached loader behavior. See [What needs the native binding](#what-needs-the-native-binding)
|
|
343
|
+
for the exceptions to fallback availability.
|
|
273
344
|
|
|
274
345
|
`sha256FileSync()` is a synchronous Node implementation in all three modes and
|
|
275
346
|
does not load the binding. Use asynchronous `sha256File()` for native hashing
|
|
@@ -281,8 +352,76 @@ Features without a safe fallback, including no-clobber
|
|
|
281
352
|
when native support is absent or off. Staging is currently Linux/macOS only and
|
|
282
353
|
rejects Windows with `unsupported-platform`.
|
|
283
354
|
|
|
355
|
+
For an ordinary direct child, native `Root.mkdir()` retains the already-admitted
|
|
356
|
+
Root directly instead of running a parent-creation walk. A missing portable
|
|
357
|
+
single-component name also skips general alias resolution after a fresh Root
|
|
358
|
+
identity check and no-follow missing-entry observation; retained Root and child
|
|
359
|
+
identity checks still surround creation. Its short descriptor
|
|
360
|
+
open, creation, identity checks, and close are synchronous; the public method
|
|
361
|
+
remains asynchronous. Root and child pathname/canonical identities are checked
|
|
362
|
+
before success. Ordinary portable paths with existing canonical parents retain the admitted
|
|
363
|
+
parent directly. A fresh exact parent snapshot must match its beneath-opened
|
|
364
|
+
descriptor; parent, child and Root name/identity checks still surround creation.
|
|
365
|
+
Ordinary missing parents use the retained descriptor walk described above when
|
|
366
|
+
the binding can create and open a child together. Aliases, private creation,
|
|
367
|
+
mutation policies, callbacks and older bindings retain their existing admission
|
|
368
|
+
paths. Policy-free `ensureRoot()` verifies the
|
|
369
|
+
retained native Root directly. In `off` and `auto`, ordinary `Root.mkdir()` keeps
|
|
370
|
+
its metadata-only creation syscall in the synchronous guard sequence; supplied
|
|
371
|
+
mutation callbacks, mutation policies, and private creation retain their existing
|
|
372
|
+
asynchronous path. Both public methods continue to return promises.
|
|
373
|
+
|
|
374
|
+
Native removal and moves also retain Root synchronously and reuse that descriptor
|
|
375
|
+
when Root is an immediate parent. Nonrecursive removal keeps its own leaf path,
|
|
376
|
+
including fresh policy, identity and post-removal checks; recursive traversal
|
|
377
|
+
retains its directory, mount, budget and cancellation checks. Neither optimization
|
|
378
|
+
changes the descriptor-relative mutation or atomic no-replace mechanism.
|
|
379
|
+
|
|
380
|
+
Ordinary `Root.ensureRoot()` in `require` can combine retained-root opening,
|
|
381
|
+
exact identity/metadata/canonical observations, the final named Root fence and
|
|
382
|
+
checked close in one native call. The initial JavaScript Root identity fence
|
|
383
|
+
remains before native lookup. Verification and close failures retain both errors;
|
|
384
|
+
an unavailable native observer hands its still-open descriptor to the existing
|
|
385
|
+
guarded observation route. Private or policy-bound calls keep their admission.
|
|
386
|
+
|
|
387
|
+
Writable opens without an explicit mutation policy attempt the existing-file
|
|
388
|
+
handle path before creating parents. Policy-bound opens first prepare the parent
|
|
389
|
+
receipt needed to select the file.
|
|
390
|
+
In `require`, missing files and parents still use native confined creation;
|
|
391
|
+
existing files keep their guarded Node handle path, identity fences and hardlink
|
|
392
|
+
checks. Required creation capabilities are checked even when the file exists.
|
|
393
|
+
|
|
394
|
+
For ordinary portable missing-file paths with an existing canonical parent,
|
|
395
|
+
native writable creation retains that parent's exact identity through beneath
|
|
396
|
+
admission. An absence observation selects exclusive native creation; an entry
|
|
397
|
+
that appears concurrently returns to the checked existing-file open. The public
|
|
398
|
+
FileHandle handoff and all post-open identity, link-count and canonical checks
|
|
399
|
+
remain in place. Explicit policies, authority callbacks, aliases and missing
|
|
400
|
+
parents retain their full admission path.
|
|
401
|
+
|
|
402
|
+
Recursive removal in both implementations retains exact directory identities
|
|
403
|
+
at admission. Repeated fences use ordinary numeric stats only when both captured
|
|
404
|
+
identity components are safely representable; larger identities keep bigint
|
|
405
|
+
observations, and unknown Windows components receive the existing bounded exact
|
|
406
|
+
retry. Every directory identity and canonical-path observation remains in place.
|
|
407
|
+
|
|
408
|
+
Native publication retains Root through synchronous descriptor open/close and
|
|
409
|
+
checks canonical staging directories against their retained descriptors. Older
|
|
410
|
+
bindings keep the existing guarded directory verification. Owned-descriptor
|
|
411
|
+
staged buffers up to 64 KiB can write synchronously when no mutation assertion is
|
|
412
|
+
configured; direct-publication buffers, larger buffers, streams, and assertion-bearing writes keep their
|
|
413
|
+
asynchronous path. Publication, permission, and post-operation identity checks
|
|
414
|
+
remain unchanged.
|
|
415
|
+
|
|
416
|
+
Asynchronous temp workspaces create their exclusive child relative to the
|
|
417
|
+
retained cleanup parent when the binding provides that operation. The returned
|
|
418
|
+
descriptor remains owned through the existing mode, parent, ancestry and final
|
|
419
|
+
pathname/descriptor checks. Collisions retry with a fresh random name and never
|
|
420
|
+
adopt or remove the existing directory. Capability probing still completes before
|
|
421
|
+
creation. Synchronous creation and older bindings retain their existing routes.
|
|
422
|
+
|
|
284
423
|
Windows owner/DACL inspection, private-directory creation, and secure-file
|
|
285
|
-
inspection support [
|
|
424
|
+
inspection support [Windows helper fallbacks](install.md#windows-security-fallback)
|
|
286
425
|
in `auto` and `off`. `require` rejects missing capabilities, and native operation
|
|
287
426
|
failures are terminal.
|
|
288
427
|
|
|
@@ -312,6 +451,78 @@ read metadata behavior are unchanged. Missing Windows pathname identity still
|
|
|
312
451
|
requires a guarded path reopen and comparison with the original retained file;
|
|
313
452
|
that fallback does not apply to POSIX no-read modes.
|
|
314
453
|
|
|
454
|
+
### Windows buffered-write cost
|
|
455
|
+
|
|
456
|
+
`Root.write(..., { durable: false })` skips durability synchronization, but still
|
|
457
|
+
stages and atomically replaces the file, admits the existing destination, and
|
|
458
|
+
checks the root, parent, published pathname and retained descriptor identities.
|
|
459
|
+
Raw Node writes truncate the destination in place and are not equivalent work.
|
|
460
|
+
|
|
461
|
+
A [Windows x64 measurement](https://github.com/openclaw/fs-safe/actions/runs/38335844371)
|
|
462
|
+
of source revision `fe216e22` used Node 24.21.0, a 16-logical-processor hosted
|
|
463
|
+
runner, and 23-byte replacements. Across 21 samples of 100 writes, uninstrumented
|
|
464
|
+
median sample means were 1.912 ms in `off` and 1.233 ms in `require`; their raw
|
|
465
|
+
Node controls were 0.246 ms and 0.255 ms. Paired raw/raw A/A ratios were 1.011
|
|
466
|
+
and 1.009. These are observations on that runner, not latency guarantees or a
|
|
467
|
+
same-process comparison between native modes.
|
|
468
|
+
|
|
469
|
+
Separate instrumentation recorded every Node filesystem and native binding call
|
|
470
|
+
over 101 warmed 22-byte replacements. The following times are median cumulative
|
|
471
|
+
microseconds per write for each call family; instrumentation adds overhead and
|
|
472
|
+
these rows must not be substituted for the uninstrumented timings above.
|
|
473
|
+
|
|
474
|
+
| Call family | `off` calls / µs | `require` calls / µs |
|
|
475
|
+
|---|---:|---:|
|
|
476
|
+
| Canonicalize pathname (`realpathSync.native`) | 14 / 797 | 4 / 265 |
|
|
477
|
+
| Exact file and directory metadata (`lstat`, `stat`, `fstat`) | 41 / 365 | 22 / 166 |
|
|
478
|
+
| Node asynchronous open | 2 / 272 | 1 / 74 |
|
|
479
|
+
| Native beneath open: parent, stage, published file | 0 / 0 | 3 / 174 |
|
|
480
|
+
| Native parent observation | 0 / 0 | 1 / 45 |
|
|
481
|
+
| Write payload | 1 / 95 | 1 / 50 |
|
|
482
|
+
| Replace by rename | 1 / 199 | 1 / 173 |
|
|
483
|
+
|
|
484
|
+
The native stage creation accounted for about 141 µs of its beneath-open total;
|
|
485
|
+
the published-file reopen accounted for about 22 µs. Native rename uses
|
|
486
|
+
handle-relative `NtSetInformationFile`, not `MoveFileEx`. Neither mode invoked
|
|
487
|
+
the security helper, queried ACLs, changed mode, or flushed a file in these
|
|
488
|
+
non-private, non-durable writes. Path and identity observations explain much of
|
|
489
|
+
the remaining cost; a hidden helper process or durability flush does not.
|
|
490
|
+
|
|
491
|
+
These measurements did not isolate antivirus/indexer time inside filesystem
|
|
492
|
+
calls and do not establish a universal NTFS lower bound. Storage, pathname depth,
|
|
493
|
+
Windows version and security software can change the result. Repeated identity
|
|
494
|
+
observations fence different admission and publication boundaries; their count
|
|
495
|
+
alone does not establish that an observation can safely be removed.
|
|
496
|
+
|
|
497
|
+
## What needs the native binding
|
|
498
|
+
|
|
499
|
+
In `off` and missing-binding `auto`, these capabilities are unavailable. Other
|
|
500
|
+
APIs keep their guarded fallback; the [delta table](#javascript-fallback-guarantees-and-delta)
|
|
501
|
+
describes its strength. Installing a binding does not add support on an excluded
|
|
502
|
+
platform or filesystem.
|
|
503
|
+
|
|
504
|
+
| API or option | Binding requirement without a fallback |
|
|
505
|
+
|---|---|
|
|
506
|
+
| `Root.move()` with `overwrite: false` (the default); `replaceDirectoryAtomic()` | `helper-unavailable`; atomic no-replace publication cannot use check-then-rename. |
|
|
507
|
+
| `stageFileInDirectory()`, `retainSymlinkInDirectory()` | Linux/macOS: `helper-unavailable`; other platforms: `unsupported-platform`. |
|
|
508
|
+
| `retainEntryForPublication()` | Linux/macOS/Windows: `helper-unavailable`; other platforms: `unsupported-platform`. |
|
|
509
|
+
| `retainFileInDirectory()` | Windows retention only; returns an unsupported admission with `helper-unavailable` rather than throwing. |
|
|
510
|
+
| `createPipe()` | Linux/macOS/FreeBSD: `helper-unavailable`; other platforms: `unsupported-platform`. |
|
|
511
|
+
| `tryAcquireWriteLease()` | Linux: `helper-unavailable`; other platforms: `unsupported-platform`. |
|
|
512
|
+
| `createCloneSource()`; `copyTree()` or `Root.copyIn()` with `clone: "always"` | `helper-unavailable`; synchronous `copyRootFileSync()` / batch copying with `clone: "always"` reports `unsupported-platform`. Ordinary automatic byte copying remains available. |
|
|
513
|
+
| `readCloneFileMetadata()`; `inspectDarwinAcl()` | macOS: metadata reading throws `helper-unavailable`; ACL inspection returns `{ kind: "unknown", reason: "helper-unavailable" }`. Elsewhere, they return unsupported/unknown facts. |
|
|
514
|
+
| `Root.copyIn()` with `sourceSymlinks: "copy-link"` | macOS/Windows: `helper-unavailable`; Linux/FreeBSD retain the guarded literal-link path. |
|
|
515
|
+
| `watch()` with `mode: "events"` | `helper-unavailable`; `mode: "auto"` uses polling. |
|
|
516
|
+
| Temp workspace/file helpers with `cleanupSafety: "require-bounded"` | `helper-unavailable`; the default `"compatible"` cleanup remains available. |
|
|
517
|
+
| Private creation on macOS | `helper-unavailable` for descriptor ACL admission: `createDirectory[Sync]()` / `createFileSync()` and Root creation with `private: true`, `Root.createJson()` and `Root.ensureRoot()` with `private: true`. |
|
|
518
|
+
| Windows test hooks `holdWindowsSharingLock()`, `setWindowsFileAttributes()`, `readWindowsFileExtents()` | Windows: `helper-unavailable`; other platforms: `unsupported-platform`. |
|
|
519
|
+
|
|
520
|
+
`preserveMetadata: true` does **not** require native support in `auto` or `off`:
|
|
521
|
+
see [copy metadata and source links](writing.md#copy-metadata-and-source-links)
|
|
522
|
+
for timestamp precision and Windows attribute differences. Archive fallbacks
|
|
523
|
+
include bundled WASM, and Windows security fallbacks use the packaged
|
|
524
|
+
prebuilt .NET Framework helper; neither requires the optional native binding.
|
|
525
|
+
|
|
315
526
|
## JavaScript fallback guarantees and delta
|
|
316
527
|
|
|
317
528
|
Policy-bound parent creation can refresh exact directory facts through the
|
|
@@ -345,20 +556,70 @@ See [Root containment guarantees](security-model.md#containment-guarantees-by-pl
|
|
|
345
556
|
| Zstd/bzip2 TAR | Rust streaming decode and fd-relative output creation. | Bundled WASM codecs feed the shared Rust TAR parser, then guarded private staging and the same merge policy; no optional codec dependency. |
|
|
346
557
|
| Publication copy | Clone, Linux `copy_file_range`, async native SHA-256. | Exclusive `wx` byte loop and Node SHA-256 with the same content/identity fences. |
|
|
347
558
|
| `rename-noreplace` | Atomic platform no-replace rename; Linux `auto` file publication can use identity-checked, non-crash-atomic link/unlink when unsupported. | Unsupported; no emulation by check-then-rename. |
|
|
348
|
-
| Windows DACL read | Direct `GetSecurityInfo`; the public facts API exposes ordered basic allow/deny ACE SIDs, masks, and decoded flags without trust policy. Secure-file reads query the borrowed open descriptor and compare its 32-bit volume serial and 64-bit file-index projection with Node's bigint receipt. | The packaged
|
|
349
|
-
| Windows private directory | Creation-time protected DACL. | The packaged
|
|
559
|
+
| Windows DACL read | Direct `GetSecurityInfo`; the public facts API exposes ordered basic allow/deny ACE SIDs, masks, and decoded flags without trust policy. Secure-file reads query the borrowed open descriptor and compare its 32-bit volume serial and 64-bit file-index projection with Node's bigint receipt. | The packaged prebuilt .NET Framework helper preserves raw facts and inspects the borrowed descriptor for secure reads, with the same Node identity comparison; its command failures reject. Structured .NET pathname reporting retains its separate compatibility query. |
|
|
560
|
+
| Windows private directory | Creation-time protected DACL. | The packaged prebuilt .NET Framework helper applies the protected DACL at creation and retains exact handles through identity validation and failure cleanup. Command failures reject. |
|
|
350
561
|
|
|
351
562
|
Use `off` in CI to keep the fallback contract exercised. Use `require` when a
|
|
352
563
|
deployment depends on the stronger mechanism or a native-only feature; do not
|
|
353
564
|
infer native loading from timing.
|
|
354
565
|
|
|
566
|
+
## Windows helper build
|
|
567
|
+
|
|
568
|
+
`pnpm build:windows-helper` builds the portable security executable with the
|
|
569
|
+
.NET SDK pinned by `global.json`, Roslyn deterministic compilation, a fixed
|
|
570
|
+
source path map, no debug symbols, AnyCPU and `Prefer32Bit=false`. The project
|
|
571
|
+
under `native/windows-security-helper/` targets .NET Framework 4.6.2 using
|
|
572
|
+
Microsoft's `Microsoft.NETFramework.ReferenceAssemblies` 1.0.3 and its committed
|
|
573
|
+
NuGet lock file; builds restore in locked mode. CI compares Ubuntu and Windows
|
|
574
|
+
SHA-256 hashes to enforce byte-identical **unsigned** output. Release builds
|
|
575
|
+
repeat this comparison before Authenticode signing on an x64 Windows runner.
|
|
576
|
+
The signing manifest records that the helper's pre-signing SHA-256 equals both
|
|
577
|
+
reproducible build hashes; its signed SHA-256 is checked against the exe inside
|
|
578
|
+
the final npm tarball. Timestamped signatures change the distributed bytes, so
|
|
579
|
+
rebuilding source is expected to reproduce the unsigned hash, not the signed
|
|
580
|
+
file. Both Windows addons and the helper must have a valid, timestamped embedded
|
|
581
|
+
signature from OpenClaw Foundation before signed x64/ARM64 runtime tests and
|
|
582
|
+
release collection. See [release prerequisites](https://github.com/openclaw/fs-safe/blob/main/RELEASE-PREREQS.md#windows-authenticode-signing-one-time-maintainer-setup)
|
|
583
|
+
for setup and the publication-free dry run. The main package includes only
|
|
584
|
+
the exe; the three C# source files remain available in the repository. The separate
|
|
585
|
+
`windows-owner-query.cs` compatibility query preserves the former PowerShell
|
|
586
|
+
query's .NET API sequence, normalization, link behavior, and locality rules;
|
|
587
|
+
the raw security bridge remains independent.
|
|
588
|
+
|
|
589
|
+
The 4.6.2 target selects modern `System.IO` path handling on 4.6.2+ runtimes,
|
|
590
|
+
matching Windows PowerShell 5.1. An executable targeting an earlier framework
|
|
591
|
+
gets legacy handling that rejects extended-length `\\?\` paths with
|
|
592
|
+
"Illegal characters in path." The helper still uses only APIs available in
|
|
593
|
+
.NET Framework 4.0 and the same CLR v4.0.30319; it needs no `.exe.config` sidecar.
|
|
594
|
+
Locked restore pins the `Microsoft.NETFramework.ReferenceAssemblies.net462`
|
|
595
|
+
package to 1.0.3 with its content hash. The raw bridge keeps extended paths out
|
|
596
|
+
of .NET normalization and uses `CreateFileW`; its security checks are unchanged.
|
|
597
|
+
|
|
598
|
+
Build output is copied beside the source modules for Vitest and beside the
|
|
599
|
+
compiled modules in `dist/`; both use the same module-relative filename.
|
|
600
|
+
`pnpm build` can skip compilation on non-Windows developer machines without the
|
|
601
|
+
SDK, retaining a previously built source-side copy when present. Packing still
|
|
602
|
+
requires the executable. CI and release assembly install the pinned SDK and
|
|
603
|
+
build it, including package smoke tests on other operating systems.
|
|
604
|
+
|
|
605
|
+
Each invocation still inspects the inherited borrowed handle with
|
|
606
|
+
`GetStdHandle(-10)` and exits. A persistent helper cannot receive replacement
|
|
607
|
+
fds opened later: portable Node exposes neither the underlying OS HANDLE nor
|
|
608
|
+
`DuplicateHandle`. Per-call processes preserve descriptor ownership and all
|
|
609
|
+
existing checks across callbacks, publication, link-count changes, and chmod.
|
|
610
|
+
There is no security-fact cache or pathname substitution for descriptor checks.
|
|
611
|
+
|
|
355
612
|
## Loader security
|
|
356
613
|
|
|
357
614
|
Native initialization runs in the async context captured when fs-safe's loader
|
|
358
615
|
module is evaluated. Import fs-safe during application startup, outside request
|
|
359
616
|
or lease scopes, so the addon's process-lifetime housekeeping cannot retain the
|
|
360
617
|
first operation's `AsyncLocalStorage` stores. Importing still does not load the
|
|
361
|
-
addon: only the first native operation
|
|
618
|
+
addon: only the first native operation or explicit
|
|
619
|
+
[`await getFsSafeNativeStatus({ probe: true })`](config.md#getfssafenativestatus-options)
|
|
620
|
+
call pays the registration cost. The `config` subpath defers the internal loader
|
|
621
|
+
until status is requested and shares that loader's cache with native operations.
|
|
622
|
+
Subsequent
|
|
362
623
|
operations and their callbacks retain their own caller context. Dynamically
|
|
363
624
|
importing fs-safe for the first time inside a request scope captures that import
|
|
364
625
|
scope instead; this boundary does not clear an already active import context.
|
|
@@ -371,9 +632,19 @@ loader conservatively attempts the glibc package and lets normal module loading
|
|
|
371
632
|
fail into `auto` fallback. The loader requires only the package selected from
|
|
372
633
|
the detected target; it never probes unrelated packages, downloads code, or
|
|
373
634
|
runs a postinstall step. A missing or incompatible binary
|
|
374
|
-
|
|
635
|
+
selects the guarded fallback in `auto`, throws typed
|
|
375
636
|
`helper-unavailable` in `require`, and is never inspected in `off`. Tests reject
|
|
376
637
|
`child_process`, `exec`, or `spawn` usage in the loader.
|
|
638
|
+
Windows security/private-creation fallbacks emit an actionable, path-free
|
|
639
|
+
warning; other fallbacks remain silent. See
|
|
640
|
+
[fallback troubleshooting](native-helper.md#troubleshooting-fallback-warnings).
|
|
641
|
+
|
|
642
|
+
## Anonymous pipes
|
|
643
|
+
|
|
644
|
+
The lean [`pipe` subpath](pipe.md) creates native-owned anonymous pipes. Linux and
|
|
645
|
+
FreeBSD use atomic close-on-exec creation; Darwin reports its non-atomic flag
|
|
646
|
+
setup explicitly. Each end must close through its native owner, including when
|
|
647
|
+
a Node stream uses it in a Worker.
|
|
377
648
|
|
|
378
649
|
## Related pages
|
|
379
650
|
|
|
@@ -42,10 +42,51 @@ POSIX remediation strings shell-quote paths with whitespace or metacharacters
|
|
|
42
42
|
and protect option-like paths with `--`, so they can be presented as commands
|
|
43
43
|
without letting the inspected pathname add shell syntax.
|
|
44
44
|
|
|
45
|
-
`inspectPathPermissions()` follows symlink targets for the effective mode but tells you whether the original path was a symlink. On POSIX it reports owner/group/world bits. On Windows it delegates to the ACL helpers below and also reports `ownerSid` plus `ownerTrusted` when ownership can be verified. `ownerTrusted` is true only for a local volume owned by the current user, LocalSystem, or built-in Administrators; remote filesystems fail closed. This remains a pathname reporting API with the fallbacks described below. `readSecureFile()` obtains descriptor-bound owner/DACL facts for the exact handle it reads, using native support or the packaged
|
|
45
|
+
`inspectPathPermissions()` follows symlink targets for the effective mode but tells you whether the original path was a symlink. On POSIX it reports owner/group/world bits. On Windows it delegates to the ACL helpers below and also reports `ownerSid` plus `ownerTrusted` when ownership can be verified. `ownerTrusted` is true only for a local volume owned by the current user, LocalSystem, or built-in Administrators; remote filesystems fail closed. This remains a pathname reporting API with the fallbacks described below. `readSecureFile()` obtains descriptor-bound owner/DACL facts for the exact handle it reads, using native support or the packaged prebuilt .NET Framework helper in `auto` and `off` modes.
|
|
46
46
|
|
|
47
47
|
## Advanced Windows ACL helpers
|
|
48
48
|
|
|
49
|
+
Private Windows DACLs grant full access to the process user, SYSTEM, and
|
|
50
|
+
Administrators. Inside an AppContainer they additionally grant that process
|
|
51
|
+
token's own container SID, with inheritance on directories. Other containers
|
|
52
|
+
and ALL APPLICATION PACKAGES receive no grant. Creation and security inspection
|
|
53
|
+
use the same token identity; a token-query failure rejects the operation.
|
|
54
|
+
Outside an AppContainer the existing descriptors are unchanged. The native
|
|
55
|
+
addon and packaged .NET security helper both enforce this boundary.
|
|
56
|
+
|
|
57
|
+
When an AppContainer cannot obtain DOS volume names, guarded path operations
|
|
58
|
+
require the native addon. fs-safe resolves the same open handle's normalized NT
|
|
59
|
+
name, then verifies its drive-root prefix before returning the DOS spelling.
|
|
60
|
+
UNC names are translated from the NT network namespace. This preserves long-name
|
|
61
|
+
canonicalization and the existing identity and containment checks without
|
|
62
|
+
granting access to other containers. Drive mappings are queried on each operation.
|
|
63
|
+
Canonicalization resolves relative inputs against the process working directory
|
|
64
|
+
before opening, and UNC inputs accept either Windows path separator spelling.
|
|
65
|
+
APIs that require absolute paths, including `readSecureFile()`, retain that requirement. The current
|
|
66
|
+
container SID is trusted during ACL inspection independently of the object's owner.
|
|
67
|
+
Folder-mounted volumes, volume-GUID inputs, and unmatched drive prefixes fail
|
|
68
|
+
closed with `FsSafeError("path-mismatch")` when DOS resolution is denied.
|
|
69
|
+
AppContainer support requires the native addon. In native mode `off`, or without
|
|
70
|
+
native membership support, a denied DOS-name query retains Node's original
|
|
71
|
+
`EPERM` error unchanged. The JavaScript realpath resolver is not a fallback.
|
|
72
|
+
Outside a sandbox, successful DOS resolution and ordinary errors retain their
|
|
73
|
+
existing behavior, including in native mode `off`.
|
|
74
|
+
|
|
75
|
+
Only a denied DOS-name query triggers the canonicalizer's in-process token probe.
|
|
76
|
+
The addon caches immutable AppContainer membership across Workers. Canonicalization
|
|
77
|
+
never starts a helper process. In a container, failure to open the path or query
|
|
78
|
+
its normalized NT name also preserves the original error, so existing contention
|
|
79
|
+
handling remains effective. Token-probe failures are typed.
|
|
80
|
+
|
|
81
|
+
Inside an AppContainer, advanced ACL inspection uses native facts for inherited
|
|
82
|
+
entries and unique full-control allow entries with uniform inheritance flags,
|
|
83
|
+
including fs-safe's private DACL. Operations that require an external helper
|
|
84
|
+
instead fail with `helper-unavailable` when native membership identifies the
|
|
85
|
+
container. Current Node/libuv child-process pipe creation can fail or hang inside
|
|
86
|
+
AppContainers ([nodejs/node#66374](https://github.com/nodejs/node/issues/66374),
|
|
87
|
+
[libuv/libuv#5178](https://github.com/libuv/libuv/issues/5178)); fs-safe must not
|
|
88
|
+
start the .NET helper or a Node helper child there.
|
|
89
|
+
|
|
49
90
|
The low-level Windows ACL parser and `icacls` command builders live in `@openclaw/fs-safe/advanced`:
|
|
50
91
|
|
|
51
92
|
```ts
|
|
@@ -69,15 +110,18 @@ resolveWindowsUserPrincipal(env);
|
|
|
69
110
|
```
|
|
70
111
|
|
|
71
112
|
The fallback Windows inspector reads the owner and DACL together through one
|
|
72
|
-
built-in
|
|
73
|
-
|
|
113
|
+
built-in prebuilt .NET Framework helper query. It runs the packaged
|
|
114
|
+
`windows-security-helper.exe` with arguments `["owner", targetPath]`, without
|
|
115
|
+
starting PowerShell. The query preserves .NET's named-path security lookup and
|
|
116
|
+
ACL normalization, including leaf-link behavior.
|
|
74
117
|
It returns canonical SIDs and numeric access masks, so Unicode paths and account
|
|
75
118
|
names do not pass through lossy console display text. `inspectWindowsAcl()` uses native descriptor facts for
|
|
76
119
|
complete local ACLs with nonzero inherited ACEs (or empty/null DACLs) when the
|
|
77
120
|
optional Windows binding is available. It applies
|
|
78
121
|
the same classifier to native facts and the fallback query, returning canonical
|
|
79
|
-
SIDs in its `principal` fields with normalized rights tokens.
|
|
80
|
-
`exec`
|
|
122
|
+
SIDs in its `principal` fields with normalized rights tokens. An explicit
|
|
123
|
+
`exec` option retains the query path; `env` no longer selects a query executable
|
|
124
|
+
or bypasses native inspection. Disabled or unavailable native helpers,
|
|
81
125
|
remote or incomplete descriptors, leaf symbolic links, and native query errors
|
|
82
126
|
use the fallback. Explicit ACEs and zero-mask entries also retain the query so
|
|
83
127
|
.NET continues to own its ACE ordering and normalization.
|
|
@@ -89,6 +133,9 @@ result. The query returns SIDs directly; no translation cache is needed.
|
|
|
89
133
|
Injected executors must return the same structured success JSON as the built-in
|
|
90
134
|
query: valid `ownerSid` and `currentUserSid` strings, an explicit boolean
|
|
91
135
|
`remote`, and complete DACL facts (`complete`, `daclPresent`, and `aces`).
|
|
136
|
+
Pass-through executors can run the supplied helper command and arguments
|
|
137
|
+
directly; canned-JSON executors use the same parser. `env` remains available for
|
|
138
|
+
principal classification and remediation command construction.
|
|
92
139
|
Missing or nonboolean locality leaves both inspectors unverified; only
|
|
93
140
|
`remote: false` establishes locality for owner trust. An explicit `remote: true`
|
|
94
141
|
retains the ACL report but never grants trusted ownership.
|
|
@@ -178,7 +225,7 @@ Object-specific and other ACE layouts are not guessed: they are omitted,
|
|
|
178
225
|
`unsupportedAceTypes`, allowing a security-sensitive caller to fail closed.
|
|
179
226
|
Non-Windows systems return `{ status: "unsupported-platform", platform }`.
|
|
180
227
|
Windows prefers the native binding. In native `auto` or `off` mode, a missing
|
|
181
|
-
binding or capability uses the packaged
|
|
228
|
+
binding or capability uses the packaged prebuilt .NET Framework helper with
|
|
182
229
|
the same raw ACE projection. Native `require` rejects either absence with
|
|
183
230
|
`FsSafeError("helper-unavailable")` and starts no command. An available native
|
|
184
231
|
query's failure is terminal. The existing coarse `inspectPathPermissions()` API
|
|
@@ -220,13 +267,13 @@ An available native capability runs all queries in one isolated process using
|
|
|
220
267
|
the current runtime executable. Its resolved native mode is forwarded explicitly;
|
|
221
268
|
Node preload and module-search environment overrides are not inherited. An
|
|
222
269
|
available native query's failure is terminal. In `auto` when the binding or
|
|
223
|
-
capability is absent, or in `off`, one packaged
|
|
224
|
-
path array from stdin
|
|
270
|
+
capability is absent, or in `off`, one packaged .NET Framework helper process reads the
|
|
271
|
+
path array from stdin. Native `require`
|
|
225
272
|
rejects missing support before launching a process. No route launches one
|
|
226
273
|
process per path or wraps synchronous parent-process queries in promises.
|
|
227
274
|
|
|
228
275
|
`timeoutMs` defaults to 60,000 and applies to the whole process, including
|
|
229
|
-
startup
|
|
276
|
+
helper startup. It must be an integer from 1 through
|
|
230
277
|
2,147,483,647. Combined stdout and stderr are limited to 16 MiB. Success waits
|
|
231
278
|
for process exit and both output pipes to close. Timeout and transport failure
|
|
232
279
|
request termination, then retain the existing one-second settlement grace.
|
|
@@ -236,7 +283,7 @@ unconfirmed child can still be reading after rejection; the operation returns
|
|
|
236
283
|
no facts and owns no output files or artifact cleanup. Neither timing out nor
|
|
237
284
|
receiving a successful kill request is reported as confirmed exit.
|
|
238
285
|
|
|
239
|
-
The
|
|
286
|
+
The helper fallback encodes and budgets each result while collecting it,
|
|
240
287
|
instead of retaining every descriptor graph until the entire batch finishes.
|
|
241
288
|
An encoded response that would exceed 16 MiB rejects with `too-large` before
|
|
242
289
|
inspecting later paths. A query failure observed earlier retains its original
|
|
@@ -266,7 +313,7 @@ On Windows, this creates the directory and applies a
|
|
|
266
313
|
protected owner + LocalSystem + Administrators full-control DACL directly with
|
|
267
314
|
an atomic security descriptor. The native route launches no command. When its
|
|
268
315
|
binding or capability is unavailable, native `auto` and `off` modes use the
|
|
269
|
-
packaged
|
|
316
|
+
packaged prebuilt .NET Framework helper. Both routes retain the parent and exact created-directory handles
|
|
270
317
|
through ACL and final pathname validation. If validation fails, it attempts only
|
|
271
318
|
nonrecursive deletion through the created handle, preserving any pathname
|
|
272
319
|
replacement. If cleanup also fails, the error retains the original failure and
|
|
@@ -300,10 +347,9 @@ pathname-only compatibility shim. Existing Windows permission inspection still
|
|
|
300
347
|
retains its structured .NET compatibility fallback.
|
|
301
348
|
|
|
302
349
|
The raw owner/DACL and private-directory fallbacks each emit one path-free
|
|
303
|
-
`FS_SAFE_NATIVE_FALLBACK` warning per process.
|
|
304
|
-
|
|
305
|
-
operations. These routes run the
|
|
306
|
-
system PowerShell policy; see the [Windows security fallback prerequisites](install.md#windows-security-fallback).
|
|
350
|
+
`FS_SAFE_NATIVE_FALLBACK` warning per process. A fresh prebuilt helper process
|
|
351
|
+
adds startup overhead to each call; install the native package for frequent
|
|
352
|
+
operations. These routes run the packaged executable under normal system policy; see the [Windows security fallback prerequisites](install.md#windows-security-fallback).
|
|
307
353
|
If command support is unavailable, disallowed, or fails, the operation rejects.
|
|
308
354
|
Private-directory creation never falls back to inherited permissions.
|
|
309
355
|
The asynchronous creation command has a 30-second deadline. After a timeout or
|
|
@@ -318,6 +364,9 @@ pathname, establish that the earlier operation stopped and verify any existing
|
|
|
318
364
|
directory's security. The library does not attempt pathname-based cleanup.
|
|
319
365
|
|
|
320
366
|
Use `createIcaclsResetCommand()` when you need a structured command and argv pair. Use `formatIcaclsResetCommand()` when you only need a remediation string for a user-facing message.
|
|
367
|
+
These builders and `formatPermissionRemediation()` throw `helper-unavailable` in
|
|
368
|
+
a known AppContainer: removing inherited grants could revoke the container's
|
|
369
|
+
own access. Ordinary-process commands remain unchanged.
|
|
321
370
|
|
|
322
371
|
## Types
|
|
323
372
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Anonymous pipes
|
|
2
|
+
|
|
3
|
+
`@openclaw/fs-safe/pipe` exposes a synchronous, native anonymous pipe for
|
|
4
|
+
one-shot input to a child process. Both ends are reopenable through
|
|
5
|
+
`/proc/self/fd/N` on Linux and `/dev/fd/N` on Darwin and FreeBSD. This supports
|
|
6
|
+
programs that open an inherited descriptor as a pathname.
|
|
7
|
+
|
|
8
|
+
On FreeBSD, reopening through `/dev/fd/N` requires `fdescfs` mounted at `/dev/fd`.
|
|
9
|
+
An administrator can mount it with `mount -t fdescfs fdesc /dev/fd`; pipe creation
|
|
10
|
+
itself does not require that mount.
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { createPipe, type OwnedPipeSync } from "@openclaw/fs-safe/pipe";
|
|
14
|
+
|
|
15
|
+
const pipe: OwnedPipeSync = createPipe();
|
|
16
|
+
try {
|
|
17
|
+
// Pass pipe.reader.fd as a numeric child_process spawn stdio entry.
|
|
18
|
+
// Keep both owners alive until their respective users finish.
|
|
19
|
+
} finally {
|
|
20
|
+
pipe.reader.close();
|
|
21
|
+
pipe.writer.close();
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The frozen result has `reader` and `writer` members of the existing
|
|
26
|
+
`OwnedFileDescriptorSync` type (`readonly fd: number`, `close(): void`, and
|
|
27
|
+
`[Symbol.dispose](): void`) and `readonly atomicCloseOnExec: boolean`.
|
|
28
|
+
Each end closes idempotently through its native owner. A close attempt consumes
|
|
29
|
+
ownership even if it throws; do not retry with a raw close or use the fd again.
|
|
30
|
+
The pair itself does not have a `close` or disposal method.
|
|
31
|
+
|
|
32
|
+
Linux and FreeBSD create both ends atomically with close-on-exec set.
|
|
33
|
+
Darwin sets the flag on both ends immediately after creation and reports
|
|
34
|
+
`atomicCloseOnExec: false`: a concurrent native fork/exec can inherit an end
|
|
35
|
+
in that short window. No JavaScript async work intervenes, but this is not an
|
|
36
|
+
atomic inheritance guarantee. Partial setup failure closes both ends.
|
|
37
|
+
|
|
38
|
+
Importing the subpath is lazy and does not load the addon or the secret-file
|
|
39
|
+
modules. Calling `createPipe()` requires a compatible native helper; disabled
|
|
40
|
+
or missing helpers throw `FsSafeError` with `helper-unavailable`. Windows and
|
|
41
|
+
other unsupported platforms throw `unsupported-platform`. Native creation
|
|
42
|
+
failures throw `helper-failed` with the original error as `cause`.
|
|
43
|
+
The cause's `code` preserves the native errno, including `EMFILE` (the process
|
|
44
|
+
descriptor limit) and `ENFILE` (the system-wide limit).
|
|
45
|
+
|
|
46
|
+
## Handing the writer to a Node stream
|
|
47
|
+
|
|
48
|
+
A native-created fd must close through its owner, including in Workers.
|
|
49
|
+
Provide the stream's custom close hook; do not let Node close it directly.
|
|
50
|
+
This is the writer handoff contract: retain the owner in the hook rather than
|
|
51
|
+
detaching its descriptor.
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { createWriteStream, write, writev } from "node:fs";
|
|
55
|
+
import { createPipe } from "@openclaw/fs-safe/pipe";
|
|
56
|
+
|
|
57
|
+
const pipe = createPipe();
|
|
58
|
+
const stream = createWriteStream("", {
|
|
59
|
+
fd: pipe.writer.fd,
|
|
60
|
+
fs: {
|
|
61
|
+
write,
|
|
62
|
+
writev,
|
|
63
|
+
close(_fd, callback) {
|
|
64
|
+
try {
|
|
65
|
+
pipe.writer.close();
|
|
66
|
+
callback(null);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
callback(error instanceof Error ? error : new Error(String(error)));
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
// After spawning with pipe.reader.fd in the child's stdio:
|
|
74
|
+
pipe.reader.close();
|
|
75
|
+
stream.end("one-shot input");
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Once the stream is created, its lifetime owns use of the writer: keep an error
|
|
79
|
+
listener, destroy it on cancellation, and wait for `close` before considering
|
|
80
|
+
the fd released. If stream construction fails, close both owners. Closing the
|
|
81
|
+
last writer delivers EOF; unread bytes remain available to the reader.
|