@openclaw/openshell-sandbox 2026.9.6 → 2026.9.8
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/assets/icon.png +0 -0
- package/dist/index.js +75 -229
- package/node_modules/@openclaw/fs-safe/CHANGELOG.md +102 -0
- package/node_modules/@openclaw/fs-safe/README.md +24 -6
- package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +6 -1
- package/node_modules/@openclaw/fs-safe/dist/advanced.js +3 -1
- package/node_modules/@openclaw/fs-safe/dist/archive-durability.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/archive-merge.js +1 -2
- package/node_modules/@openclaw/fs-safe/dist/archive-plan.d.ts +2 -7
- package/node_modules/@openclaw/fs-safe/dist/archive-read.js +9 -18
- package/node_modules/@openclaw/fs-safe/dist/archive-staging.d.ts +1 -2
- package/node_modules/@openclaw/fs-safe/dist/archive-staging.js +3 -3
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-directory.js +4 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-entry.d.ts +11 -11
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-entry.js +3 -35
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-integrity.d.ts +2 -2
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-integrity.js +2 -12
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-loader.d.ts +7 -3
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-loader.js +13 -10
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-manifest.js +3 -1
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-preflight.d.ts +2 -3
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-preflight.js +16 -8
- package/node_modules/@openclaw/fs-safe/dist/archive.d.ts +6 -3
- package/node_modules/@openclaw/fs-safe/dist/archive.js +22 -19
- package/node_modules/@openclaw/fs-safe/dist/atomic.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/copy-publication.d.ts +1 -3
- package/node_modules/@openclaw/fs-safe/dist/copy-publication.js +2 -6
- package/node_modules/@openclaw/fs-safe/dist/deny-mutation-match.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/deny-mutation-match.js +71 -0
- package/node_modules/@openclaw/fs-safe/dist/deny-mutations.js +4 -3
- package/node_modules/@openclaw/fs-safe/dist/directory-guard.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/directory-guard.js +0 -3
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.d.ts +20 -2
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.js +77 -1
- package/node_modules/@openclaw/fs-safe/dist/directory-receipt.js +5 -7
- package/node_modules/@openclaw/fs-safe/dist/effective-uid.js +1 -4
- package/node_modules/@openclaw/fs-safe/dist/errors.d.ts +3 -1
- package/node_modules/@openclaw/fs-safe/dist/errors.js +3 -2
- package/node_modules/@openclaw/fs-safe/dist/file-identity.js +10 -2
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-acquisition.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-admission.js +5 -1
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-held.d.ts +4 -11
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-held.js +1 -4
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-io.d.ts +3 -6
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-io.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-options.d.ts +2 -10
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root.d.ts +2 -4
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/file-store-boundary.d.ts +3 -7
- package/node_modules/@openclaw/fs-safe/dist/file-store-boundary.js +7 -10
- package/node_modules/@openclaw/fs-safe/dist/file-store-prune.js +7 -6
- package/node_modules/@openclaw/fs-safe/dist/file-store.d.ts +4 -7
- package/node_modules/@openclaw/fs-safe/dist/file-store.js +19 -21
- package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +23 -31
- package/node_modules/@openclaw/fs-safe/dist/guest.js +21 -12
- package/node_modules/@openclaw/fs-safe/dist/json-document-store.d.ts +4 -9
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-ownership.d.ts +1 -5
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue.js +2 -7
- package/node_modules/@openclaw/fs-safe/dist/local-file-access.js +2 -5
- package/node_modules/@openclaw/fs-safe/dist/local-file-descriptor.d.ts +2 -5
- package/node_modules/@openclaw/fs-safe/dist/local-roots.d.ts +2 -7
- package/node_modules/@openclaw/fs-safe/dist/move-path-cleanup.js +4 -4
- package/node_modules/@openclaw/fs-safe/dist/mutation-authority.js +5 -0
- package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +51 -14
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +8 -2
- package/node_modules/@openclaw/fs-safe/dist/native-staged-symlink.d.ts +13 -0
- package/node_modules/@openclaw/fs-safe/dist/native-staged-symlink.js +303 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch-worker.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch-worker.js +54 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch.d.ts +5 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch.js +64 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl.js +3 -0
- package/node_modules/@openclaw/fs-safe/dist/path.js +17 -1
- package/node_modules/@openclaw/fs-safe/dist/permission-exec.d.ts +6 -0
- package/node_modules/@openclaw/fs-safe/dist/permission-exec.js +3 -6
- package/node_modules/@openclaw/fs-safe/dist/permissions-public.d.ts +3 -1
- package/node_modules/@openclaw/fs-safe/dist/permissions-public.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/permissions-windows.d.ts +3 -6
- package/node_modules/@openclaw/fs-safe/dist/permissions.d.ts +3 -10
- package/node_modules/@openclaw/fs-safe/dist/permissions.js +0 -1
- package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-admission.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-admission.js +4 -2
- package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-shared-route.d.ts +2 -8
- package/node_modules/@openclaw/fs-safe/dist/pinned-open.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/pinned-open.js +2 -3
- package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +4 -0
- package/node_modules/@openclaw/fs-safe/dist/read-opened-file.d.ts +2 -5
- package/node_modules/@openclaw/fs-safe/dist/regular-file.js +3 -3
- package/node_modules/@openclaw/fs-safe/dist/replace-directory.js +5 -17
- package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.js +36 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.d.ts +4 -10
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.js +66 -38
- package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.d.ts +7 -11
- package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +10 -2
- package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.d.ts +21 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.js +123 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-mutation.d.ts +26 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-mutation.js +47 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.d.ts +2 -2
- package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.js +16 -4
- package/node_modules/@openclaw/fs-safe/dist/replace-file-types.d.ts +55 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file.d.ts +3 -55
- package/node_modules/@openclaw/fs-safe/dist/replace-file.js +31 -11
- package/node_modules/@openclaw/fs-safe/dist/retained-directory-replacement.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/retained-directory-replacement.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/retained-file-types.d.ts +50 -0
- package/node_modules/@openclaw/fs-safe/dist/retained-file-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/retained-file.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/retained-file.js +121 -0
- package/node_modules/@openclaw/fs-safe/dist/root-context.js +3 -2
- package/node_modules/@openclaw/fs-safe/dist/root-directory-entry.d.ts +9 -0
- package/node_modules/@openclaw/fs-safe/dist/root-directory-entry.js +28 -0
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.d.ts +9 -1
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +88 -37
- package/node_modules/@openclaw/fs-safe/dist/root-file-final-admission.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/root-file-final-admission.js +1 -6
- package/node_modules/@openclaw/fs-safe/dist/root-file.js +2 -5
- package/node_modules/@openclaw/fs-safe/dist/root-handle-context.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/root-handle-context.js +12 -0
- package/node_modules/@openclaw/fs-safe/dist/root-impl.d.ts +3 -3
- package/node_modules/@openclaw/fs-safe/dist/root-impl.js +8 -5
- package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.d.ts +2 -7
- package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +3 -3
- package/node_modules/@openclaw/fs-safe/dist/root-observed-path.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/root-observed-path.js +0 -3
- package/node_modules/@openclaw/fs-safe/dist/root-path-observation.d.ts +4 -11
- package/node_modules/@openclaw/fs-safe/dist/root-path.js +7 -10
- package/node_modules/@openclaw/fs-safe/dist/root-paths.d.ts +2 -6
- package/node_modules/@openclaw/fs-safe/dist/root-remove-identity.d.ts +1 -3
- package/node_modules/@openclaw/fs-safe/dist/root-walk.d.ts +19 -12
- package/node_modules/@openclaw/fs-safe/dist/root-walk.js +56 -20
- package/node_modules/@openclaw/fs-safe/dist/root-write-admission.js +1 -6
- package/node_modules/@openclaw/fs-safe/dist/root-write-complete-parent.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/root-write-complete-parent.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.js +8 -2
- package/node_modules/@openclaw/fs-safe/dist/secret-file.d.ts +6 -2
- package/node_modules/@openclaw/fs-safe/dist/secret-file.js +12 -16
- package/node_modules/@openclaw/fs-safe/dist/secret-read-policy.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/secure-file-windows.js +5 -14
- package/node_modules/@openclaw/fs-safe/dist/secure-file.js +3 -2
- package/node_modules/@openclaw/fs-safe/dist/secure-temp-dir.js +2 -7
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js +4 -4
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-admission-parser.d.ts +1 -2
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-handle.d.ts +2 -8
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-policy.d.ts +2 -7
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.d.ts +2 -2
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js +11 -12
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-stale-admission.d.ts +1 -5
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock.js +9 -7
- package/node_modules/@openclaw/fs-safe/dist/staged-symlink-types.d.ts +39 -0
- package/node_modules/@openclaw/fs-safe/dist/staged-symlink-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/stat-observation.js +3 -10
- package/node_modules/@openclaw/fs-safe/dist/strict-file-identity.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/strict-file-identity.js +6 -6
- package/node_modules/@openclaw/fs-safe/dist/symlink-parents.js +58 -7
- package/node_modules/@openclaw/fs-safe/dist/temp-target.js +8 -10
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-admission.js +28 -33
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.js +16 -17
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-descriptor.js +1 -2
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-owner.js +4 -9
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.js +52 -0
- package/node_modules/@openclaw/fs-safe/dist/test-hooks.d.ts +5 -1
- package/node_modules/@openclaw/fs-safe/dist/text-atomic.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/text-atomic.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/trash.js +27 -1
- package/node_modules/@openclaw/fs-safe/dist/walk.d.ts +2 -5
- package/node_modules/@openclaw/fs-safe/dist/watch-alias.d.ts +6 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-alias.js +88 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-hints.d.ts +9 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-hints.js +93 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-native.d.ts +36 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-native.js +73 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-scan.d.ts +28 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-scan.js +300 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-stream.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-stream.js +32 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-types.d.ts +60 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/watch.d.ts +5 -0
- package/node_modules/@openclaw/fs-safe/dist/watch.js +530 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-owner.d.ts +3 -7
- package/node_modules/@openclaw/fs-safe/dist/windows-owner.js +0 -1
- package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.cs +6 -6
- package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.ps1 +78 -3
- package/node_modules/@openclaw/fs-safe/dist/windows-security-command.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-security-command.js +66 -15
- package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.js +6 -2
- package/node_modules/@openclaw/fs-safe/docs/advanced.md +4 -2
- package/node_modules/@openclaw/fs-safe/docs/archive.md +16 -0
- package/node_modules/@openclaw/fs-safe/docs/atomic.md +93 -3
- package/node_modules/@openclaw/fs-safe/docs/contributing.md +102 -4
- package/node_modules/@openclaw/fs-safe/docs/copy.md +2 -0
- package/node_modules/@openclaw/fs-safe/docs/durability.md +7 -0
- package/node_modules/@openclaw/fs-safe/docs/file-store.md +5 -0
- package/node_modules/@openclaw/fs-safe/docs/guest.md +7 -1
- package/node_modules/@openclaw/fs-safe/docs/index.md +1 -0
- package/node_modules/@openclaw/fs-safe/docs/install.md +30 -0
- package/node_modules/@openclaw/fs-safe/docs/native-helper.md +20 -4
- package/node_modules/@openclaw/fs-safe/docs/native.md +63 -1
- package/node_modules/@openclaw/fs-safe/docs/permissions.md +66 -0
- package/node_modules/@openclaw/fs-safe/docs/public-api.md +30 -3
- package/node_modules/@openclaw/fs-safe/docs/retained-file.md +115 -0
- package/node_modules/@openclaw/fs-safe/docs/root.md +35 -9
- package/node_modules/@openclaw/fs-safe/docs/secret-file.md +19 -0
- package/node_modules/@openclaw/fs-safe/docs/security-model.md +4 -1
- package/node_modules/@openclaw/fs-safe/docs/sidecar-lock.md +5 -1
- package/node_modules/@openclaw/fs-safe/docs/staged-symlink.md +124 -0
- package/node_modules/@openclaw/fs-safe/docs/store.md +3 -1
- package/node_modules/@openclaw/fs-safe/docs/temp.md +24 -4
- package/node_modules/@openclaw/fs-safe/docs/testing.md +210 -0
- package/node_modules/@openclaw/fs-safe/docs/types.md +6 -0
- package/node_modules/@openclaw/fs-safe/docs/walk.md +34 -1
- package/node_modules/@openclaw/fs-safe/docs/watch.md +251 -0
- package/node_modules/@openclaw/fs-safe/docs/writing.md +21 -0
- package/node_modules/@openclaw/fs-safe/package.json +13 -9
- 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-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-x64-msvc/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-win32-x64-msvc/package.json +1 -1
- package/node_modules/p-limit/index.d.ts +2 -0
- package/node_modules/p-limit/index.js +17 -1
- package/node_modules/p-limit/package.json +1 -1
- package/node_modules/p-limit/readme.md +2 -0
- package/package.json +6 -6
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-owner.d.ts +0 -20
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-owner.js +0 -78
|
@@ -130,10 +130,22 @@ features, including strict owned-tree temp cleanup, retained-directory staging,
|
|
|
130
130
|
and atomic `rename-noreplace` (including the default no-clobber `Root.move()`),
|
|
131
131
|
remain unavailable. Operations without a safe fallback fail with `helper-unavailable`
|
|
132
132
|
when the matching package is absent, incompatible, or disabled.
|
|
133
|
+
No-clobber `Root.move()` preserves a native loader failure in the error's
|
|
134
|
+
`cause`, including the original missing-library or incompatible-glibc diagnostic.
|
|
133
135
|
|
|
134
136
|
Upgrading an existing 0.5 consumer? Follow [Migrating to 0.6](migrating-to-0.6.md)
|
|
135
137
|
before deploying with native mode `require` or native-only features.
|
|
136
138
|
|
|
139
|
+
### Older Linux kernels and seccomp
|
|
140
|
+
|
|
141
|
+
The native addon supports kernels without `openat2` (before Linux 5.6) and
|
|
142
|
+
containers returning `ENOSYS` or probe-time `EPERM` for that syscall. Beneath
|
|
143
|
+
opens use a guarded no-follow component walk and report `best-effort`.
|
|
144
|
+
Nested no-clobber moves retain atomic `renameat2(RENAME_NOREPLACE)` and exact
|
|
145
|
+
identity checks; keep native mode enabled. Strict bounded cleanup still
|
|
146
|
+
requires `openat2` with `RESOLVE_NO_XDEV` and reports `helper-unavailable`
|
|
147
|
+
without it. See [Linux capability behavior and limits](native.md#linux-without-openat2).
|
|
148
|
+
|
|
137
149
|
### Windows security fallback
|
|
138
150
|
|
|
139
151
|
Windows raw owner/DACL inspection, private-directory creation, and secure-file
|
|
@@ -161,6 +173,24 @@ available native operation's error never triggers a command retry. See
|
|
|
161
173
|
|
|
162
174
|
## Native helper policy
|
|
163
175
|
|
|
176
|
+
### Supported native platforms
|
|
177
|
+
|
|
178
|
+
Prebuilt bindings cover Linux x64/arm64 (GNU glibc **2.28 or newer**, or musl),
|
|
179
|
+
macOS x64/arm64, and Windows x64. The GNU baseline includes RHEL 8, Rocky Linux 8,
|
|
180
|
+
and AlmaLinux 8. A compatible Node 22+ runtime and the kernel/filesystem features
|
|
181
|
+
required by each operation are still necessary; a loadable addon alone does not
|
|
182
|
+
guarantee every native capability. Systems older than glibc 2.28 are outside the
|
|
183
|
+
GNU binary support floor.
|
|
184
|
+
|
|
185
|
+
A missing or incompatible optional binding (including `ERR_DLOPEN_FAILED` from
|
|
186
|
+
glibc) does not prevent importing fs-safe. In `auto`, supported JavaScript/WASM
|
|
187
|
+
fallbacks remain available. Native-only operations such as the default
|
|
188
|
+
no-clobber `Root.move()` still fail closed with `helper-unavailable`; `require`
|
|
189
|
+
also rejects fallback-capable operations and retains the binding load error as
|
|
190
|
+
the cause.
|
|
191
|
+
|
|
192
|
+
### Loading modes
|
|
193
|
+
|
|
164
194
|
The platform native binaries provide fd-relative open/link/mkdir primitives,
|
|
165
195
|
atomic no-replace rename, and file identity checks. The default is `auto`: use
|
|
166
196
|
the matching binary when it loads, otherwise use the guarded JavaScript path
|
|
@@ -103,10 +103,16 @@ clone/copy/hash workers, POSIX canonicalization, and Windows security descriptor
|
|
|
103
103
|
layer owns policy, retries, filters, budgets, modes, cleanup, error
|
|
104
104
|
normalization, and the decision to fall back.
|
|
105
105
|
|
|
106
|
-
- Linux uses `openat2` with `RESOLVE_BENEATH | RESOLVE_NO_MAGICLINKS`, `renameat`, and `renameat2(RENAME_NOREPLACE)`. Direct-child no-replace renames borrow already-retained parent descriptors; deeper relative paths retain the guarded reopen. Owned-tree cleanup
|
|
106
|
+
- Linux uses `openat2` with `RESOLVE_BENEATH | RESOLVE_NO_MAGICLINKS`, `renameat`, and `renameat2(RENAME_NOREPLACE)`. Without `openat2`, beneath opens use the [no-follow component walk](native.md#linux-without-openat2) with exact identity checks and report `best-effort`. Direct-child no-replace renames borrow already-retained parent descriptors; deeper relative paths retain the guarded reopen. Owned-tree cleanup still requires `openat2` with `RESOLVE_NO_XDEV` and fails closed when unavailable.
|
|
107
107
|
- macOS 15.4 and newer prefer `O_RESOLVE_BENEATH`; older kernels resolve components with `O_NOFOLLOW` and restart in-root symlinks from the pinned root descriptor. Both routes use an `F_GETPATH` post-open escape detector and report `best-effort` because directory rename races are not atomic with that check. Direct-child no-replace renames borrow already-retained parents without another `F_GETPATH`; deeper paths retain the guarded reopen. Publication uses `renameat` for replacement and `renameatx_np(RENAME_EXCL)` for no-replace; owned-tree cleanup uses descriptor-relative `openat`/`unlinkat`.
|
|
108
108
|
- Windows uses handle-relative `NtCreateFile`, rejects reparse points during root-bounded traversal, and uses `FileRenameInfoEx` with replacement selected explicitly by the TypeScript policy layer. The dedicated retained-directory `renameNoReplaceWithIdentity` primitive compares its required exact source receipt with the handle opened for rename before mutation. Its internal mismatch status is normalized to public `path-mismatch`; the legacy four-argument `renameNoReplace` export and its other callers are unchanged. Owned trees are deleted through exact opened handles with `FileDispositionInfoEx`; symlink/reparse entries in owned trees are removed as leaves and never traversed. Descriptors crossing N-API are converted only by the host executable's paired `uv_get_osfhandle` and `uv_open_osfhandle` exports. A runtime without both exports is unsupported for these native operations; the binding never guesses a raw HANDLE or uses a foreign CRT descriptor table. Descriptor-producing operations also require that same host's synchronous libuv close and request-management APIs before exporting an owned descriptor.
|
|
109
109
|
|
|
110
|
+
Linux root lookups reject negative descriptor sentinels before borrowing a handle or resolving a relative path; they never substitute the process working directory for an admitted root. Public Root operations already supply retained, admitted handles.
|
|
111
|
+
|
|
112
|
+
On Linux and macOS, native asynchronous file-copy admission rejects negative source and parent descriptors before creating a stage, preserving source-before-parent error ordering. Callers must keep nonnegative source and parent descriptors open until the operation settles.
|
|
113
|
+
|
|
114
|
+
Low-level Unix query, hash, copy, clone, staging, and owned-tree cleanup calls also reject negative descriptors before using them, preserving each operation's path-validation, cancellation, and cleanup order. Descriptor-relative mutations never accept a working-directory sentinel as a retained capability. On modern macOS, beneath opens reject negative roots with `EBADF` before calling `openat`, rather than returning `EIO` after an OS failure or working-directory operation. This check does not establish the validity of arbitrary nonnegative integers: callers must supply live descriptors and retain them until synchronous calls return or asynchronous operations settle.
|
|
115
|
+
|
|
110
116
|
`replaceDirectoryAtomic()` requires `renameNoReplaceWithIdentity` before it
|
|
111
117
|
creates a missing target parent. On POSIX the dedicated entry point keeps the
|
|
112
118
|
existing pre-dispatch exact receipt fence but dispatches direct-child names
|
|
@@ -133,11 +139,12 @@ for the exact difference.
|
|
|
133
139
|
The guarded JavaScript mutation path is detection-based, not containment-atomic.
|
|
134
140
|
If a same-privilege peer can replace a writable parent after its identity guard
|
|
135
141
|
but before Node resolves a pathname mutation, the mutation can land outside the
|
|
136
|
-
intended root before the post-operation guard throws.
|
|
137
|
-
|
|
142
|
+
intended root before the post-operation guard throws. Native `require` ensures the addon is present, but does not require a
|
|
143
|
+
`kernel-atomic` resolver; inspect containment and use OS isolation when that
|
|
144
|
+
concurrent attacker is part of the threat model.
|
|
138
145
|
|
|
139
146
|
`openBeneath()` returns `{ fd, containment }`. `containment` is
|
|
140
|
-
`"kernel-atomic"` for Linux `openat2` and `"best-effort"` for macOS and
|
|
147
|
+
`"kernel-atomic"` for Linux `openat2` and `"best-effort"` for the Linux fallback, macOS and
|
|
141
148
|
Windows. Public JavaScript root open/read/writable results also expose the
|
|
142
149
|
field and report `"best-effort"`; the label reports mechanism, not policy.
|
|
143
150
|
|
|
@@ -175,3 +182,12 @@ consumer performs its 0.5 upgrade.
|
|
|
175
182
|
- [Durability](durability.md)
|
|
176
183
|
- [Migrating to 0.5](migrating-to-0.5.md)
|
|
177
184
|
- [Migrating to 0.6](migrating-to-0.6.md)
|
|
185
|
+
|
|
186
|
+
### Retained existing Windows file
|
|
187
|
+
|
|
188
|
+
The maintained Windows helper supports the public
|
|
189
|
+
[`retainFileInDirectory`](retained-file.md) lifecycle on fixed local NTFS.
|
|
190
|
+
Private native handles, exact identity/generation checks, writable-section
|
|
191
|
+
admission and explicit close results stay behind that public API. Older helpers
|
|
192
|
+
without this capability are unsupported; there is no pathname deletion fallback.
|
|
193
|
+
No Windows namespace persistence barrier is provided.
|
|
@@ -43,6 +43,7 @@ whether a path, archive entry, mode, owner, or cleanup policy is acceptable.
|
|
|
43
43
|
|
|
44
44
|
- Linux uses `openat2(RESOLVE_BENEATH | RESOLVE_NO_MAGICLINKS)`, fd-relative
|
|
45
45
|
`mkdirat`/`linkat`/`renameat`/`renameat2`, `FICLONE`, and `copy_file_range`.
|
|
46
|
+
Without `openat2`, beneath opens use the [guarded fallback](#linux-without-openat2).
|
|
46
47
|
- macOS 15.4 and newer first use `openat(O_RESOLVE_BENEATH)`; older kernels walk
|
|
47
48
|
components with `openat(O_NOFOLLOW)` and restart in-root symlinks from the
|
|
48
49
|
pinned root. Both routes apply an `F_GETPATH` post-open containment detector,
|
|
@@ -85,6 +86,67 @@ privately owned empty ACL. Unsupported, malformed, and failed inspection is not
|
|
|
85
86
|
reported as absence. These facts do not classify individual ACE permissions or
|
|
86
87
|
prove volume ownership enforcement; each caller applies its own security policy.
|
|
87
88
|
|
|
89
|
+
## Linux without openat2
|
|
90
|
+
|
|
91
|
+
Linux kernels before 5.6 and containers whose seccomp policy denies `openat2`
|
|
92
|
+
can keep native mode `auto` or `require`. The addon caches one harmless
|
|
93
|
+
`openat2(".")` capability probe per process. `ENOSYS`, or `EPERM` on that probe,
|
|
94
|
+
selects the native `openat` fallback. An `EPERM`/`EACCES` from an application
|
|
95
|
+
operation is still a permission error and never triggers a retry. Install
|
|
96
|
+
syscall filters before the first native operation; a later `ENOSYS` fails with
|
|
97
|
+
`ENOTSUP`, rather than changing the cached mechanism during a call.
|
|
98
|
+
|
|
99
|
+
The fallback inspects components with `O_PATH | O_NOFOLLOW`, follows relative
|
|
100
|
+
symlink targets using a stack of retained directory descriptors, and rejects
|
|
101
|
+
absolute targets or `..` past the retained root with `EXDEV`. It permits at most
|
|
102
|
+
40 link expansions (`ELOOP` beyond that limit). Final `O_NOFOLLOW` and exclusive
|
|
103
|
+
creation retain their syscall semantics, including opening the link itself with
|
|
104
|
+
`O_PATH | O_NOFOLLOW` and creating through a dangling in-root relative link.
|
|
105
|
+
Exact device/inode/type identities are checked before and after the final
|
|
106
|
+
no-follow open; followed links also retain their descriptors and have their
|
|
107
|
+
named identities and target strings rechecked. Detected replacements fail with
|
|
108
|
+
`EXDEV`, including changes to directories left behind by a link's `..` target.
|
|
109
|
+
|
|
110
|
+
The fallback also honors `nosymfollow` mount restrictions on retained links.
|
|
111
|
+
Two conservative restrictions remain: the fallback refuses to follow **any
|
|
112
|
+
procfs symlink** with `ELOOP`, including ordinary links such as `/proc/mounts`.
|
|
113
|
+
Userspace metadata cannot distinguish these from procfs magic links, which
|
|
114
|
+
`RESOLVE_NO_MAGICLINKS` must never follow. Opening a final link itself with
|
|
115
|
+
`O_PATH | O_NOFOLLOW` remains allowed. In a sticky, world-writable directory,
|
|
116
|
+
the fallback refuses all symlink following with `EACCES`, even when the calling
|
|
117
|
+
thread or directory owner owns the link, or `fs.protected_symlinks=0`. This
|
|
118
|
+
preserves Linux's protected-symlink restriction without assuming the thread's
|
|
119
|
+
filesystem UID or treating equal mapped `stat` UIDs as proof of equal kernel
|
|
120
|
+
owners (distinct unmapped owners can both appear as the overflow UID).
|
|
121
|
+
|
|
122
|
+
Public Root policy and canonical-path admission, hardlink rejection, pinned-file checks, and mutation
|
|
123
|
+
identity fences remain in place. `openBeneath()` reports `best-effort`: these
|
|
124
|
+
identity samples detect replacements but cannot make a multi-component walk
|
|
125
|
+
atomic against a hostile process renaming directories between samples. This
|
|
126
|
+
is the same documented containment class as the macOS and JavaScript paths;
|
|
127
|
+
applications requiring atomic beneath resolution must check the result or use
|
|
128
|
+
OS isolation. Rejection after a mutating open does not promise rollback.
|
|
129
|
+
|
|
130
|
+
Nested no-clobber `Root.move()` still admits both parents and uses
|
|
131
|
+
`renameat2(RENAME_NOREPLACE)`. Existing destinations are never overwritten.
|
|
132
|
+
That separate syscall/filesystem capability remains required; if unavailable,
|
|
133
|
+
the operation fails with `helper-unavailable`. Turning native mode `off` still
|
|
134
|
+
disables no-clobber moves because Node has no equivalent atomic rename API.
|
|
135
|
+
|
|
136
|
+
Bounded owned-tree cleanup deliberately has no `openat` fallback:
|
|
137
|
+
`RESOLVE_NO_XDEV` rejects bind mounts even when device numbers match, which
|
|
138
|
+
ordinary identity checks cannot reproduce. `cleanupSafety: "require-bounded"`
|
|
139
|
+
fails before workspace creation with `helper-unavailable`; low-level cleanup
|
|
140
|
+
opens report `ENOTSUP`. Compatible cleanup retains its documented behavior.
|
|
141
|
+
Low-level `O_TMPFILE` anonymous opens also fail before creation with `ENOTSUP`
|
|
142
|
+
in the fallback, because named-entry identity checks cannot verify an unnamed
|
|
143
|
+
file. Public staged-write APIs use exclusive named files and remain available.
|
|
144
|
+
|
|
145
|
+
For tests, set `FS_SAFE_TEST_NO_OPENAT2=1` before starting Node to force the
|
|
146
|
+
fallback (including bounded-cleanup refusal). It is read only at the first
|
|
147
|
+
capability probe. It has no effect on macOS or Windows.
|
|
148
|
+
See [Linux fallback testing](testing.md#linux-openat2-fallback).
|
|
149
|
+
|
|
88
150
|
## Archives
|
|
89
151
|
|
|
90
152
|
Native ZIP and TAR entry reads retain a private, unpooled input Buffer in
|
|
@@ -252,7 +314,7 @@ See [Root containment guarantees](security-model.md#containment-guarantees-by-pl
|
|
|
252
314
|
|
|
253
315
|
| Capability | Native path | Guarded JavaScript path |
|
|
254
316
|
|---|---|---|
|
|
255
|
-
| Native beneath opens and Root mutations | Descriptor-relative beneath operations. Pinned writes create parents and publish both replacement and no-replace targets relative to open directory descriptors. No-clobber `Root.move()` admits both parents and uses the native no-replace rename. Native `openBeneath()` reports `kernel-atomic`
|
|
317
|
+
| Native beneath opens and Root mutations | Descriptor-relative beneath operations. Pinned writes create parents and publish both replacement and no-replace targets relative to open directory descriptors. No-clobber `Root.move()` admits both parents and uses the native no-replace rename. Native `openBeneath()` reports `kernel-atomic` with Linux `openat2` and `best-effort` with the guarded Linux fallback, macOS, and Windows. macOS uses `O_RESOLVE_BENEATH` when available plus an `F_GETPATH` detector, while Windows rejects reparse traversal in the object-manager call. | Reports `best-effort`: component-wise alias checks, no-follow opens where Node exposes them, private temp/rename, and post-operation identity verification. No-clobber `Root.move()` is unsupported because a check followed by a replacing rename is unsafe. A same-privilege peer can replace a writable parent after a guard assertion but before Node resolves another pathname mutation; the mutation may land outside the intended root before the post-check detects it. |
|
|
256
318
|
| ZIP/TAR/gzip | Rust streaming decode and fd-relative output creation. | Optional JSZip or bundled WASM TAR into guarded private staging, then the same guarded merge policy. |
|
|
257
319
|
| 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. |
|
|
258
320
|
| 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. |
|
|
@@ -185,6 +185,72 @@ the same raw ACE projection. Native `require` rejects either absence with
|
|
|
185
185
|
query's failure is terminal. The existing coarse `inspectPathPermissions()` API
|
|
186
186
|
still owns its compatibility fallback and trust classification.
|
|
187
187
|
|
|
188
|
+
### Asynchronous batches
|
|
189
|
+
|
|
190
|
+
Use `readOwnerAndDaclBatch()` when several paths, such as a directory and its
|
|
191
|
+
ancestors, need inspection without blocking the caller's event loop:
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
import { readOwnerAndDaclBatch } from "@openclaw/fs-safe/permissions";
|
|
195
|
+
|
|
196
|
+
const facts = await readOwnerAndDaclBatch(stagingDirectories, { timeoutMs: 60_000 });
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
function readOwnerAndDaclBatch(
|
|
201
|
+
paths: readonly string[],
|
|
202
|
+
options?: { timeoutMs?: number },
|
|
203
|
+
): Promise<OwnerAndDaclResult[]>;
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Results use the same raw fact shape and SID spelling as `readOwnerAndDacl()`.
|
|
207
|
+
They correspond one-for-one to input order, including duplicate paths. An empty
|
|
208
|
+
array returns `[]` without dispatch. Other platforms return an
|
|
209
|
+
`unsupported-platform` result for each path. Query failures, malformed facts,
|
|
210
|
+
or missing/reordered response rows reject the entire batch; no partial facts
|
|
211
|
+
are returned. Null DACLs, incomplete ACE lists and nonlocal observations remain
|
|
212
|
+
raw facts for the caller's policy to evaluate.
|
|
213
|
+
|
|
214
|
+
The call captures paths, options and native mode before awaiting. Windows
|
|
215
|
+
relative paths are anchored to the current directory or selected drive at
|
|
216
|
+
entry, without normalizing their remaining `.` or `..` components. Empty,
|
|
217
|
+
nonstring, sparse or NUL-containing path entries and Windows namespace aliases
|
|
218
|
+
reject before dispatch. The UTF-8 JSON input is limited to 16 MiB.
|
|
219
|
+
|
|
220
|
+
An available native capability runs all queries in one isolated process using
|
|
221
|
+
the current runtime executable. Its resolved native mode is forwarded explicitly;
|
|
222
|
+
Node preload and module-search environment overrides are not inherited. An
|
|
223
|
+
available native query's failure is terminal. In `auto` when the binding or
|
|
224
|
+
capability is absent, or in `off`, one packaged PowerShell process reads the
|
|
225
|
+
path array from stdin and compiles the existing C# bridge once. Native `require`
|
|
226
|
+
rejects missing support before launching a process. No route launches one
|
|
227
|
+
process per path or wraps synchronous parent-process queries in promises.
|
|
228
|
+
|
|
229
|
+
`timeoutMs` defaults to 60,000 and applies to the whole process, including
|
|
230
|
+
startup and fallback compilation. It must be an integer from 1 through
|
|
231
|
+
2,147,483,647. Combined stdout and stderr are limited to 16 MiB. Success waits
|
|
232
|
+
for process exit and both output pipes to close. Timeout and transport failure
|
|
233
|
+
request termination, then retain the existing one-second settlement grace.
|
|
234
|
+
The error's `processExitConfirmed` field, also retained in its cause receipt,
|
|
235
|
+
distinguishes observed exit from an unconfirmed termination attempt. An
|
|
236
|
+
unconfirmed child can still be reading after rejection; the operation returns
|
|
237
|
+
no facts and owns no output files or artifact cleanup. Neither timing out nor
|
|
238
|
+
receiving a successful kill request is reported as confirmed exit.
|
|
239
|
+
|
|
240
|
+
The PowerShell fallback encodes and budgets each result while collecting it,
|
|
241
|
+
instead of retaining every descriptor graph until the entire batch finishes.
|
|
242
|
+
An encoded response that would exceed 16 MiB rejects with `too-large` before
|
|
243
|
+
inspecting later paths. A query failure observed earlier retains its original
|
|
244
|
+
error, and no partial facts are returned. Duplicate paths remain independent
|
|
245
|
+
observations. This bounds accumulated encoded results, not total process memory:
|
|
246
|
+
the current descriptor and row, decoded input, and runtime overhead still exist.
|
|
247
|
+
|
|
248
|
+
Batch observations are point-in-time pathname facts, not a snapshot or a
|
|
249
|
+
retained filesystem capability. The caller still owns ancestry trust, principal
|
|
250
|
+
policy and authorization for subsequent operations. Existing singular queries
|
|
251
|
+
and other command operations retain their 30-second deadline, 1 MiB output
|
|
252
|
+
budget and documented failure behavior.
|
|
253
|
+
|
|
188
254
|
## Private directories
|
|
189
255
|
|
|
190
256
|
```ts
|
|
@@ -41,6 +41,11 @@ The lexical path surface additionally exports `isNodeError`,
|
|
|
41
41
|
`UnsafeDeviceReadPathMatch`, `UnsafeDeviceReadPathOptions`, and
|
|
42
42
|
`UnsafeDeviceReadPathReason`.
|
|
43
43
|
|
|
44
|
+
`isPathRelativeEscape()` rejects absolute paths and relative paths that step
|
|
45
|
+
above their starting directory at any point. Contained paths such as
|
|
46
|
+
`dir/../file` remain relative. It accepts both separators on Windows; on POSIX,
|
|
47
|
+
a backslash remains a literal filename character.
|
|
48
|
+
|
|
44
49
|
The advanced root-file primitive exports `OpenRootFileParams`,
|
|
45
50
|
`OpenRootFileSyncParams`, `RootFileOpenResult`, and
|
|
46
51
|
`RootFileOpenFailureReason`. These are composition types for callers building
|
|
@@ -74,6 +79,16 @@ missing relative suffixes beneath an existing directory using bounded temporary
|
|
|
74
79
|
directory probes. The caller owns Unicode-pair policy, caching, and the fallback
|
|
75
80
|
for `undefined`. See [path suffix alias probing](path-suffix-aliases.md).
|
|
76
81
|
|
|
82
|
+
`retainSymlinkInDirectory` holds an explicitly identified POSIX symlink through
|
|
83
|
+
exact-slot no-replace publication; its receipts use the `StagedSymlink*` and
|
|
84
|
+
`PublishedSymlinkReceipt` types. See [staged symlinks](staged-symlink.md).
|
|
85
|
+
|
|
86
|
+
`retainFileInDirectory` retains an existing Windows NTFS file through a native
|
|
87
|
+
handle for explicit identity-bound retirement. It is described by
|
|
88
|
+
`RetainFileInDirectoryOptions`, `RetainedFile`, `RetainedFileAdmission`,
|
|
89
|
+
`RetainedFileExpected`, `RetainedFileIssue`, `RetainedFileReceipt`, and
|
|
90
|
+
`RetainedFileResult`. See [retained Windows files](retained-file.md).
|
|
91
|
+
|
|
77
92
|
## Guest source
|
|
78
93
|
|
|
79
94
|
`@openclaw/fs-safe/guest` exports `GUEST_FILESYSTEM_PYTHON`,
|
|
@@ -100,7 +115,8 @@ best-effort cleanup helper used by those queue flows.
|
|
|
100
115
|
Permission inspection exposes `PermissionCheckOptions` and `SafeStatResult`.
|
|
101
116
|
Private-directory creation uses `CreatePrivateDirectoryOptions`. Raw Windows
|
|
102
117
|
descriptor facts use `OwnerAndDaclResult`, `WindowsAccessControlEntry`, and
|
|
103
|
-
`WindowsAceFlags`.
|
|
118
|
+
`WindowsAceFlags`. `readOwnerAndDaclBatch()` returns those same facts in input
|
|
119
|
+
order through an isolated asynchronous batch with a whole-process timeout.
|
|
104
120
|
|
|
105
121
|
Secure reads split their option and result shapes into
|
|
106
122
|
`SecureFileTrustOptions`, `SecureFilePermissionOptions`,
|
|
@@ -126,8 +142,11 @@ parent/workspace descriptors; see the
|
|
|
126
142
|
Atomic helper option and receipt types include
|
|
127
143
|
`MovePathWithCopyFallbackOptions`, `ReplaceDirectoryAtomicOptions`,
|
|
128
144
|
`ReplaceFileAtomicSyncOptions`, `ReplaceFileAtomicResult`,
|
|
129
|
-
`ReplaceFileAtomicRestoreCleanup`, `ReplaceFileCopyFallbackRestorePolicy`,
|
|
130
|
-
`ReplaceFileDestinationHardlinkPolicy`.
|
|
145
|
+
`ReplaceFileAtomicRestoreCleanup`, `ReplaceFileCopyFallbackRestorePolicy`,
|
|
146
|
+
`ReplaceFileDestinationHardlinkPolicy`, and `ReplaceFileAtomicDestinationState`.
|
|
147
|
+
The `assertBeforeMutation` option rechecks caller authority before new effects,
|
|
148
|
+
and `onDestinationState` reports retained destination identities as
|
|
149
|
+
`ReplaceFileAtomicDestinationState` values; see [atomic writes](atomic.md).
|
|
131
150
|
|
|
132
151
|
The durability surface also exports the synchronous strict
|
|
133
152
|
`syncDirectorySync`, plus `DirectoryReceipt`, `DurableDirectoryReceipt`,
|
|
@@ -154,6 +173,14 @@ Archive option and policy types are `ExtractArchiveOptions`,
|
|
|
154
173
|
used by extractors. `resolvePackedRootDir` finds the single packed root when an
|
|
155
174
|
archive layout permits it; neither helper weakens entry validation.
|
|
156
175
|
|
|
176
|
+
## `watch`
|
|
177
|
+
|
|
178
|
+
`@openclaw/fs-safe/watch` exports `watch()` plus `WatchScope`, `WatchEntry`,
|
|
179
|
+
`WatchChange`, `WatchInvalidation`, `WatchFailure`, `WatchHealth`,
|
|
180
|
+
`WatchOptions`, and `WatchSubscription`. Invalidations are advisory; guarded
|
|
181
|
+
scans stay authoritative. See [filesystem observation](watch.md) for modes,
|
|
182
|
+
budgets, `persistent`, and lifecycle.
|
|
183
|
+
|
|
157
184
|
## Keeping this list honest
|
|
158
185
|
|
|
159
186
|
Every runtime and type name in `test/public-api.json` must appear somewhere in
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Retained Windows files
|
|
3
|
+
description: "Explicit identity-bound retirement of an existing file; resource settlement is not persistence."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retain an existing Windows file
|
|
7
|
+
|
|
8
|
+
`retainFileInDirectory` from `@openclaw/fs-safe/advanced` retains **one existing
|
|
9
|
+
regular direct child**, without creating or deleting anything at admission.
|
|
10
|
+
It requires the matching native package. There is no pathname-unlink fallback.
|
|
11
|
+
The initial implementation supports fixed local NTFS drives only; other systems
|
|
12
|
+
return `unsupported`.
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { retainFileInDirectory } from "@openclaw/fs-safe/advanced";
|
|
16
|
+
|
|
17
|
+
const admission = retainFileInDirectory({
|
|
18
|
+
directory: producer.directory, // canonical C:\... spelling
|
|
19
|
+
parent: producer.parentIdentity, // exact bigint dev/ino
|
|
20
|
+
basename: producer.basename,
|
|
21
|
+
expected: producer.expected, // bigint dev/ino/size/mtimeNs/ctimeNs and SHA-256
|
|
22
|
+
assertBeforeMutation: () => producer.assertCurrentExclusiveAuthority(),
|
|
23
|
+
});
|
|
24
|
+
if (admission.status === "retained") {
|
|
25
|
+
using file = admission.file;
|
|
26
|
+
const outcome = file.remove(); // explicit; using alone does NOT delete
|
|
27
|
+
// outcome.persistence is always "not-proven".
|
|
28
|
+
producer.recordRetirementObservation(outcome);
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The producer must capture its expected identity, generation and bytes during its
|
|
33
|
+
own creation/ownership protocol. Reading an arbitrary current pathname immediately
|
|
34
|
+
before admission does **not** authenticate the producer. `Number` identities,
|
|
35
|
+
zero/unknown identities, negative/overflowing identities, and malformed digests
|
|
36
|
+
are rejected. A full opaque native volume/file ID is included in the receipt;
|
|
37
|
+
no native handle or numeric descriptor is exposed. Receipts are immutable facts,
|
|
38
|
+
not transferable authority.
|
|
39
|
+
|
|
40
|
+
## Public types
|
|
41
|
+
|
|
42
|
+
- `RetainFileInDirectoryOptions`: immutable admission inputs and synchronous authority.
|
|
43
|
+
- `RetainedFileExpected`: exact producer identity, write generation and expected digest.
|
|
44
|
+
- `RetainedFileAdmission`: a retained `RetainedFile` or a refusal/settlement result.
|
|
45
|
+
- `RetainedFileReceipt`: immutable admitted facts, never reusable authority.
|
|
46
|
+
- `RetainedFileResult`: separate disposition, namespace, resources and persistence facts.
|
|
47
|
+
- `RetainedFileIssue`: phase, native code/message and optional original authority cause.
|
|
48
|
+
|
|
49
|
+
## Admission and mutation custody
|
|
50
|
+
|
|
51
|
+
The directory's ancestry is opened component by component without following
|
|
52
|
+
reparse points, with delete sharing denied. Its exact expected parent identity
|
|
53
|
+
and canonical path are checked. The file is opened relative to that retained
|
|
54
|
+
parent, with write/delete sharing denied. Existing writer handles refuse
|
|
55
|
+
admission; a read-oplock grant also excludes preexisting writable mapped
|
|
56
|
+
sections. That oplock request is cancelled and joined before admission returns,
|
|
57
|
+
while the no-write-sharing file handle remains open. No detached request remains.
|
|
58
|
+
|
|
59
|
+
The opened file must match the expected exact NTFS identity, single-link regular
|
|
60
|
+
type, write/change generation, size and SHA-256. Names with stream/device syntax,
|
|
61
|
+
trailing-dot/space aliases and observed named data streams are unsupported.
|
|
62
|
+
Readonly attributes and ACL denials are not repaired or overridden. Verification
|
|
63
|
+
is synchronous and bounded by `maxBytes` (default 16 MiB, maximum 64 MiB).
|
|
64
|
+
|
|
65
|
+
The original producer must retain **exclusive mutation custody for the entire
|
|
66
|
+
file**, including alternate streams, attributes, security and hardlink creation.
|
|
67
|
+
Windows read/write sharing is per-stream; it is not an application lock over
|
|
68
|
+
all possible aliases. The helper rejects observed alternate streams and changed
|
|
69
|
+
generations, but does not turn a point-in-time check into exclusion of concurrent
|
|
70
|
+
alias/attribute operations. The synchronous `assertBeforeMutation` must check
|
|
71
|
+
that this original authority is still current. Callers unable to establish that
|
|
72
|
+
custody must not call `remove`. Privileged/raw-volume/kernel modifications are
|
|
73
|
+
outside this capability's threat model.
|
|
74
|
+
|
|
75
|
+
`remove()` admits authority once, revalidates the retained object, sets native
|
|
76
|
+
handle disposition, closes the file, observes the name under the still-retained
|
|
77
|
+
parent, then closes ancestry. No pathname is passed to unlink. Ordinary
|
|
78
|
+
`dispose()` and `[Symbol.dispose]()` only close resources. Authority callbacks
|
|
79
|
+
returning Promises, thenables, or synchronous or asynchronous generator objects
|
|
80
|
+
are refused without deletion; generators are never advanced. Reentrancy is also
|
|
81
|
+
refused, and even caught reentrancy poisons that attempt.
|
|
82
|
+
Repeated settlement returns the original receipt without another mutation or
|
|
83
|
+
another authority call. A copied receipt cannot be used to remove a replacement.
|
|
84
|
+
`[Symbol.dispose]()` throws `FsSafeError` with the complete result in
|
|
85
|
+
`error.details.result` if resource settlement is uncertain; `dispose()` returns
|
|
86
|
+
that result directly. GC closes only and produces no settlement receipt; use
|
|
87
|
+
explicit disposal.
|
|
88
|
+
|
|
89
|
+
## Read the facts separately
|
|
90
|
+
|
|
91
|
+
| Field | Meaning |
|
|
92
|
+
| --- | --- |
|
|
93
|
+
| `status` | `unsupported`, `not-attempted`, `preserved-mismatch`, `disposition-accepted`, `name-absent-after-settlement`, `failed`, or `indeterminate`. |
|
|
94
|
+
| `disposition` | Whether native deletion was unattempted, accepted, rejected, or indeterminate. |
|
|
95
|
+
| `namespace` | A post-file-close observation: absent, original, foreign, unknown, or not observed. Absence alone never authenticates prior deletion. |
|
|
96
|
+
| `resources` | `closed` or `close-failed`; operation and close errors are retained together. An uncertain close is never retried against a possibly recycled handle. |
|
|
97
|
+
| `persistence` | Always `not-proven`; no namespace persistence barrier was performed. |
|
|
98
|
+
|
|
99
|
+
An accepted disposition can still have an unknown/foreign/original namespace
|
|
100
|
+
observation or failed close. A later replacement may exist even after observed
|
|
101
|
+
absence. Existing read handles can retain access to old bytes after the name is
|
|
102
|
+
absent. Namespace observation does not certify all foreign handles are closed.
|
|
103
|
+
An unexpected native binding failure is indeterminate, not success.
|
|
104
|
+
|
|
105
|
+
## No persistence or service-transaction guarantee
|
|
106
|
+
|
|
107
|
+
This API does not flush a volume, implement a journal, certify power-loss-safe
|
|
108
|
+
unlink, or issue an application dependency release. Process-termination tests
|
|
109
|
+
establish process-owned handle lifetime only, not crash/storage durability.
|
|
110
|
+
|
|
111
|
+
An update transaction must independently qualify either a real namespace
|
|
112
|
+
persistence barrier or original-owned durable recovery with correct ordering,
|
|
113
|
+
generation binding and restart consumption. Until then it must retain its
|
|
114
|
+
uncertain recovery/dependency state. Successful removal of one payload does not
|
|
115
|
+
establish a committed multi-file transaction or justify deleting its receipt.
|
|
@@ -24,7 +24,7 @@ type RootDefaults = {
|
|
|
24
24
|
denyMutations?: DenyMutationPolicy; // absolute paths/prefixes mutation methods may not change
|
|
25
25
|
maxBytes?: number; // refuse reads larger than this many bytes; defaults to 16 MiB
|
|
26
26
|
mkdir?: boolean; // create missing parent dirs on write/openWritable/append; default true
|
|
27
|
-
mode?: number; // file mode
|
|
27
|
+
mode?: number; // requested file mode; per-call override available
|
|
28
28
|
nonBlockingRead?: boolean; // compatibility hint; safe opens are already nonblocking where supported
|
|
29
29
|
renameIdentity?: "strict" | "verify-content-with-lock"; // default "strict"
|
|
30
30
|
symlinks?: "reject" | "follow-within-root" | "follow-parents-within-root"; // read policy
|
|
@@ -68,7 +68,10 @@ fs.walk(rel, options) // root-bounded AsyncIterable<{ relativePath, kin
|
|
|
68
68
|
|
|
69
69
|
`walk()` is the incremental, root-bounded recursive scan. It supports entry and
|
|
70
70
|
depth budgets, cancellation, and `symlinkPolicy: "skip" |
|
|
71
|
-
"follow-within-root"
|
|
71
|
+
"follow-within-root" | "include"`. Include mode returns links as
|
|
72
|
+
`{ relativePath, kind: "symlink", size }` without resolving or entering their
|
|
73
|
+
targets, including dangling and outside-root links. `size` describes the link,
|
|
74
|
+
not its target. With an entry budget, sorted walks prepare small metadata
|
|
72
75
|
batches within the remaining budget; unbounded sorted walks reuse the full
|
|
73
76
|
directory snapshot.
|
|
74
77
|
The default `order: "sorted"` enumerates and sorts each directory's names;
|
|
@@ -92,6 +95,8 @@ Directory reads remain fail-fast by default. With
|
|
|
92
95
|
`{ relativePath, kind: "directory-error", size: 0, error }` and continues with
|
|
93
96
|
the remaining tree. That policy also covers identity-check failures after an
|
|
94
97
|
awaited filter, while callback failures always reject.
|
|
98
|
+
In include mode, a directory that becomes a symlink before descent is a
|
|
99
|
+
`path-mismatch` directory error; it is never silently omitted or followed.
|
|
95
100
|
See [Directory walking](walk.md) for the pure-Node guarantees and the contrast
|
|
96
101
|
with the standalone best-effort walkers.
|
|
97
102
|
|
|
@@ -117,6 +122,10 @@ created through a directory symlink or Windows junction. An absolute path
|
|
|
117
122
|
outside the root is still rejected. On Windows, alternate casing is accepted
|
|
118
123
|
only when the differently cased Root prefix has the Root's exact directory
|
|
119
124
|
identity; the operation then continues under the trusted Root spelling.
|
|
125
|
+
Absolute paths keep literal `~` components: `readAbsolute("/srv/root/~/file")`
|
|
126
|
+
reads that entry under the root, without expanding the user's home directory.
|
|
127
|
+
Relative `~/file` inputs still expand the home directory and must remain inside
|
|
128
|
+
the Root; use `./~/file` for a literal relative `~` directory.
|
|
120
129
|
|
|
121
130
|
### Writes
|
|
122
131
|
|
|
@@ -153,6 +162,11 @@ await fs.create("private-data/credential", "synthetic credential", { private: tr
|
|
|
153
162
|
|
|
154
163
|
`write`, `create`, `append`, `writeJson`, and `createJson` accept `mode?: number`; use `0o600` for credentials and other private state. `writeJson` also accepts the same options as `JSON.stringify` plus `trailingNewline?: boolean` (defaults `true` so the file ends in `\n`).
|
|
155
164
|
|
|
165
|
+
For `append` and `openWritable`, `mode` only affects new-file creation: POSIX
|
|
166
|
+
permissions remain subject to the process umask. These methods do not chmod
|
|
167
|
+
existing files. Replacement and create-only writes apply their final mode
|
|
168
|
+
through the retained descriptor; see [Writing](writing.md#write-options).
|
|
169
|
+
|
|
156
170
|
Buffered `create` and `createJson` also accept `atomic?: boolean`. With `true`,
|
|
157
171
|
complete content is staged before exclusive publication even in native-off mode;
|
|
158
172
|
the fallback requires hardlinks. Omitted or `false` keeps the existing buffered
|
|
@@ -249,8 +263,10 @@ occurred, later cancellation or verification failure preserves the destination.
|
|
|
249
263
|
The synchronous optional `onDestinationPublished` callback receives a frozen
|
|
250
264
|
`RootCopyPublicationReceipt` containing `{ path, dev, ino }`, with exact bigint identity immediately after
|
|
251
265
|
publication, before later checks can fail. Callback errors also preserve the
|
|
252
|
-
published file.
|
|
253
|
-
|
|
266
|
+
published file. Promise, thenable, and synchronous or asynchronous generator
|
|
267
|
+
results reject with `TypeError`; returned generators are never advanced. Other
|
|
268
|
+
synchronous return values are ignored. This receipt records an outcome; it does
|
|
269
|
+
not authorize removing a file that another actor may have edited. Application recovery and cooperative
|
|
254
270
|
locking remain caller-owned.
|
|
255
271
|
|
|
256
272
|
Existing `copyIn` callers must account for completed destinations retained after
|
|
@@ -310,10 +326,12 @@ truncation, append, move, and removal. Buffered writes use bounded chunks and
|
|
|
310
326
|
recheck before every partial-write submission; file removal submits a direct
|
|
311
327
|
unlink request. Native calls that perform multiple filesystem steps are one
|
|
312
328
|
dispatch. No asynchronous wait separates the check
|
|
313
|
-
from that dispatch. A thrown value rejects the operation unchanged;
|
|
314
|
-
or
|
|
315
|
-
|
|
316
|
-
|
|
329
|
+
from that dispatch. A thrown value rejects the operation unchanged; a Promise,
|
|
330
|
+
thenable, or synchronous or asynchronous generator result rejects with `TypeError`
|
|
331
|
+
before that mutation. Returned generators are never advanced. Other synchronous
|
|
332
|
+
return values are ignored. Generator detection applies to generator objects
|
|
333
|
+
themselves, not proxy wrappers. Callbacks can run multiple times and must inspect
|
|
334
|
+
current authority during each call; return-value validation does not establish it.
|
|
317
335
|
Directory creation rechecks the retained parent after the callback and before
|
|
318
336
|
submitting mkdir, so a replacement is rejected before creating that component.
|
|
319
337
|
Overwrite moves recheck the retained root, parents, source identity and both
|
|
@@ -340,7 +358,9 @@ For `openWritable()`, the callback covers the library's parent creation,
|
|
|
340
358
|
exclusive creation, and truncation. The returned raw `FileHandle` belongs to
|
|
341
359
|
the caller, which must check authority before its own later writes.
|
|
342
360
|
|
|
343
|
-
All mutation methods accept `denyMutations?: { paths?: string[]; prefixes?: string[] }`. Entries must be absolute paths. `paths` blocks those exact paths; `prefixes` blocks those paths and their descendants. fs-safe preserves path strings exactly and canonicalizes through existing ancestors before comparing, so a symlinked ancestor to a denied location is still denied.
|
|
361
|
+
All mutation methods accept `denyMutations?: { paths?: string[]; prefixes?: string[] }`. Entries must be absolute paths. `paths` blocks those exact paths; `prefixes` blocks those paths and their descendants. fs-safe preserves path strings exactly and canonicalizes through existing ancestors before comparing, so a symlinked ancestor to a denied location is still denied. Missing suffixes also match prospective case and canonical Unicode normalization aliases: case-folding and Unicode NFC/NFD-equivalent spellings cannot bypass a denied path or prefix. A read-only observation of the existing parent can establish that ASCII case variants are distinct. Admission never creates temporary probe files or directories. If sensitivity cannot be established (including empty or unreadable parents, future directories, and Unicode normalization), equivalent suffixes are denied conservatively, even on a filesystem that would allow distinct names. Distinct existing canonical ancestors and unrelated names remain distinct. Observations are local to each synchronous policy check and are refreshed after callbacks or mutations. This conservative fallback does not model other filesystem-specific equivalences, such as HFS+ ignorable formatting characters.
|
|
362
|
+
|
|
363
|
+
Denied mutations throw `FsSafeError` with code `denied-path`. Use this for caller-specific sensitive paths, not as a replacement for the root boundary, symlink, or hardlink checks.
|
|
344
364
|
|
|
345
365
|
`move()` snapshots its merged default and per-call mutation policy before
|
|
346
366
|
asynchronous preparation. Later changes to the original policy objects or arrays
|
|
@@ -375,6 +395,12 @@ fs.entries(rel, options?) // nonrecursive AsyncIterable<DirEntry>, includ
|
|
|
375
395
|
fs.resolve(rel) // absolute path inside the root, after canonicalization
|
|
376
396
|
```
|
|
377
397
|
|
|
398
|
+
Directory enumeration (`list`, `entries`, and `walk`) requires UTF-8 filenames.
|
|
399
|
+
A discovered name that cannot be represented losslessly as a JavaScript string
|
|
400
|
+
rejects with `invalid-path`, before looking up metadata through that name.
|
|
401
|
+
Streaming traversal may already have yielded earlier entries. Literal Unicode
|
|
402
|
+
replacement characters (`U+FFFD`) remain valid names.
|
|
403
|
+
|
|
378
404
|
These do not pin a later operation. During `stat()`, the exact selected target and
|
|
379
405
|
parent are checked around metadata collection; `list()` checks one exact selected
|
|
380
406
|
directory around the complete name/metadata batch instead of repeating containment
|
|
@@ -113,6 +113,10 @@ If an already validated descriptor fails while reading, both readers throw an
|
|
|
113
113
|
operational `FsSafeError` with `code: "read-failed"`; inspect `cause` for the
|
|
114
114
|
underlying Node filesystem code such as `EIO`.
|
|
115
115
|
|
|
116
|
+
Caught `null` or `undefined` inspection and read failures are reported as
|
|
117
|
+
structured errors with an `Error` cause carrying `"null"` or `"undefined"`,
|
|
118
|
+
instead of an internal `TypeError` while inspecting the thrown value.
|
|
119
|
+
|
|
116
120
|
A synchronous reader closes its descriptor once. A close failure preserves an
|
|
117
121
|
earlier read or identity-validation error; after a successful read, the close
|
|
118
122
|
failure is reported before trimming or rejecting empty content.
|
|
@@ -137,6 +141,11 @@ startWebhookVerifier(signingKey);
|
|
|
137
141
|
|
|
138
142
|
Async. Creates the parent directory at `dirMode` (default `0o700`) if missing, writes content to a sibling temp file, finalizes `mode` (default `0o600`) through an owned descriptor after content writes, and atomically renames over the destination. Publication verification checks the final file identity and mode.
|
|
139
143
|
|
|
144
|
+
Both secret writers capture top-level parameter values when called, before
|
|
145
|
+
asynchronous filesystem preparation; a supplied byte buffer is captured by
|
|
146
|
+
reference. A parameter getter throwing `null` or `undefined` rejects with that
|
|
147
|
+
same value before filesystem inspection.
|
|
148
|
+
|
|
140
149
|
On POSIX, both native and JavaScript writers verify actual `0o600` permission
|
|
141
150
|
bits through the retained descriptor before writing content. A filesystem that
|
|
142
151
|
reports successful chmod without enforcing those bits fails with
|
|
@@ -219,6 +228,15 @@ if anything already occupies the target path it throws
|
|
|
219
228
|
`FsSafeError("secret-exists")` without modifying that entry. Use the distinct
|
|
220
229
|
name when first-writer-wins is part of the credential protocol.
|
|
221
230
|
|
|
231
|
+
Its `durable` option also accepts `"file"`, matching `Root.create()`. This
|
|
232
|
+
requires every file `fsync` to succeed, including on `EPERM`, while parent-directory
|
|
233
|
+
synchronization remains best effort. The default and boolean options retain their
|
|
234
|
+
existing behavior. `writeSecretFileAtomic()` continues to accept boolean durability.
|
|
235
|
+
|
|
236
|
+
Strict file synchronization preserves the existing publication strategy and
|
|
237
|
+
identity-checked cleanup. A failed file flush before staged publication prevents
|
|
238
|
+
publication; a failure after publication can leave the complete file present.
|
|
239
|
+
|
|
222
240
|
Distinct leaves can share missing-parent creation without a `secret-exists`
|
|
223
241
|
error. Concurrent creates at the same leaf still have exactly one winner;
|
|
224
242
|
the loser receives `secret-exists` and leaves the winner's bytes intact.
|
|
@@ -235,6 +253,7 @@ try {
|
|
|
235
253
|
rootDir: "/var/lib/app/credentials",
|
|
236
254
|
filePath: "/var/lib/app/credentials/provider.refresh-token",
|
|
237
255
|
content: refreshToken,
|
|
256
|
+
durable: "file",
|
|
238
257
|
});
|
|
239
258
|
} catch (error) {
|
|
240
259
|
if (!(error instanceof FsSafeError) || error.code !== "secret-exists") throw error;
|
|
@@ -232,13 +232,16 @@ The library does not modify or constrain the global Node.js `fs` namespace, and
|
|
|
232
232
|
|
|
233
233
|
| Mechanism | Reported containment | Boundary |
|
|
234
234
|
|---|---|---|
|
|
235
|
-
| Linux native | `kernel-atomic` | `openat2(RESOLVE_BENEATH | RESOLVE_NO_MAGICLINKS)` resolves and opens under the root in one kernel operation. |
|
|
235
|
+
| Linux native with `openat2` | `kernel-atomic` | `openat2(RESOLVE_BENEATH | RESOLVE_NO_MAGICLINKS)` resolves and opens under the root in one kernel operation. |
|
|
236
|
+
| Linux native without `openat2` | `best-effort` | A no-follow `openat` component walk retains each parent and verifies exact identity associations before and after the final open; all symlink components are rejected. |
|
|
236
237
|
| macOS native | `best-effort` | macOS 15.4 and newer use `O_RESOLVE_BENEATH` first; older kernels use the guarded `openat(O_NOFOLLOW)` component walk. Both verify the opened descriptor with `F_GETPATH`. |
|
|
237
238
|
| Windows native | `best-effort` | Handle-relative `NtCreateFile` rejects reparse points, but this package does not claim a Linux-style atomic beneath guarantee. |
|
|
238
239
|
| JavaScript fallback | `best-effort` | Canonical checks, no-follow opens where Node exposes them, and post-open identity checks form a check-then-use sequence. |
|
|
239
240
|
|
|
240
241
|
The macOS `F_GETPATH` verification is an escape detector, not a race-atomic guarantee. A hostile same-UID process can rename a directory after `O_RESOLVE_BENEATH` or the manual walk and race the post-open sample or a later descriptor-relative mutation. The native result therefore remains `best-effort` on macOS even when the kernel flag is available. No policy decision is attached to these labels; callers can inspect the fact and decide what their own threat model requires.
|
|
241
242
|
|
|
243
|
+
The Linux fallback's identity checks are also detection-based: a directory can be renamed between chain samples or before a later descriptor-relative mutation. It cannot provide atomic resolution, and rejection after a mutating open does not promise rollback. See [Linux without openat2](native.md#linux-without-openat2) for the cached capability probe, conservative symlink rejection, and operations that remain unavailable.
|
|
244
|
+
|
|
242
245
|
The public `OpenResult`, `ReadResult`, and `WritableOpenResult` expose `containment`. Those root APIs currently report `best-effort`; direct native `openBeneath()` reports the platform value above. No-replace publication uses `renameat2(RENAME_NOREPLACE)` on Linux, `renameatx_np(RENAME_EXCL)` on macOS, and `FileRenameInfoEx` with replacement disabled on Windows, but those separate mutation semantics do not upgrade an open result's containment label.
|
|
243
246
|
|
|
244
247
|
## Limitations to keep in mind
|