@openclaw/fs-safe 0.15.0 → 0.16.0

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.
Files changed (184) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +28 -6
  3. package/dist/advanced.d.ts +1 -0
  4. package/dist/advanced.d.ts.map +1 -1
  5. package/dist/advanced.js +1 -0
  6. package/dist/archive-kind.d.ts +0 -1
  7. package/dist/archive-kind.d.ts.map +1 -1
  8. package/dist/archive-kind.js +5 -17
  9. package/dist/archive-parser.wasm +0 -0
  10. package/dist/archive-read.d.ts.map +1 -1
  11. package/dist/archive-read.js +6 -7
  12. package/dist/archive-tar-stream.d.ts +3 -0
  13. package/dist/archive-tar-stream.d.ts.map +1 -1
  14. package/dist/archive-tar-stream.js +56 -37
  15. package/dist/archive-tar-wasm.d.ts +16 -4
  16. package/dist/archive-tar-wasm.d.ts.map +1 -1
  17. package/dist/archive-tar-wasm.js +134 -34
  18. package/dist/archive.d.ts.map +1 -1
  19. package/dist/archive.js +5 -6
  20. package/dist/clone-metadata.d.ts +1 -0
  21. package/dist/clone-metadata.d.ts.map +1 -1
  22. package/dist/clone-metadata.js +6 -2
  23. package/dist/create-directory.d.ts +20 -0
  24. package/dist/create-directory.d.ts.map +1 -0
  25. package/dist/create-directory.js +130 -0
  26. package/dist/create-file-async.d.ts +7 -0
  27. package/dist/create-file-async.d.ts.map +1 -0
  28. package/dist/create-file-async.js +121 -0
  29. package/dist/create-file.d.ts +8 -0
  30. package/dist/create-file.d.ts.map +1 -0
  31. package/dist/create-file.js +190 -0
  32. package/dist/create-owned-file.d.ts +8 -0
  33. package/dist/create-owned-file.d.ts.map +1 -0
  34. package/dist/create-owned-file.js +16 -0
  35. package/dist/create.d.ts +4 -0
  36. package/dist/create.d.ts.map +1 -0
  37. package/dist/create.js +2 -0
  38. package/dist/creation-darwin.d.ts +7 -0
  39. package/dist/creation-darwin.d.ts.map +1 -0
  40. package/dist/creation-darwin.js +79 -0
  41. package/dist/creation-file-state.d.ts +19 -0
  42. package/dist/creation-file-state.d.ts.map +1 -0
  43. package/dist/creation-file-state.js +118 -0
  44. package/dist/creation-path.d.ts +21 -0
  45. package/dist/creation-path.d.ts.map +1 -0
  46. package/dist/creation-path.js +71 -0
  47. package/dist/creation-permissions.d.ts +19 -0
  48. package/dist/creation-permissions.d.ts.map +1 -0
  49. package/dist/creation-permissions.js +125 -0
  50. package/dist/directory-durability.d.ts +1 -1
  51. package/dist/directory-durability.d.ts.map +1 -1
  52. package/dist/directory-durability.js +22 -80
  53. package/dist/directory-guard.d.ts +3 -0
  54. package/dist/directory-guard.d.ts.map +1 -1
  55. package/dist/directory-mode-node.d.ts +2 -0
  56. package/dist/directory-mode-node.d.ts.map +1 -1
  57. package/dist/directory-mode-node.js +8 -0
  58. package/dist/directory-receipt.d.ts +24 -0
  59. package/dist/directory-receipt.d.ts.map +1 -0
  60. package/dist/directory-receipt.js +127 -0
  61. package/dist/file-cleanup.d.ts +19 -0
  62. package/dist/file-cleanup.d.ts.map +1 -0
  63. package/dist/file-cleanup.js +78 -0
  64. package/dist/file-observation.d.ts +1 -1
  65. package/dist/file-observation.d.ts.map +1 -1
  66. package/dist/file-store-boundary.d.ts +2 -6
  67. package/dist/file-store-boundary.d.ts.map +1 -1
  68. package/dist/file-store-boundary.js +3 -9
  69. package/dist/file-store-sync-write.d.ts.map +1 -1
  70. package/dist/file-store-sync-write.js +2 -5
  71. package/dist/guarded-mkdir.d.ts +1 -0
  72. package/dist/guarded-mkdir.d.ts.map +1 -1
  73. package/dist/guarded-mkdir.js +36 -7
  74. package/dist/move-path.js +1 -1
  75. package/dist/native-binding.d.ts +11 -1
  76. package/dist/native-binding.d.ts.map +1 -1
  77. package/dist/native-fallback-warning.d.ts +4 -0
  78. package/dist/native-fallback-warning.d.ts.map +1 -0
  79. package/dist/native-fallback-warning.js +11 -0
  80. package/dist/native-operations.d.ts +0 -2
  81. package/dist/native-operations.d.ts.map +1 -1
  82. package/dist/native-operations.js +0 -24
  83. package/dist/native-parent-admission.d.ts +2 -0
  84. package/dist/native-parent-admission.d.ts.map +1 -1
  85. package/dist/native-parent-admission.js +3 -2
  86. package/dist/native-pinned-write-windows.d.ts +1 -1
  87. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  88. package/dist/native-pinned-write-windows.js +173 -28
  89. package/dist/native-pinned-write.d.ts.map +1 -1
  90. package/dist/native-pinned-write.js +19 -3
  91. package/dist/native-policy-parent-windows.d.ts.map +1 -1
  92. package/dist/native-policy-parent-windows.js +15 -6
  93. package/dist/native-staged-file.d.ts +3 -2
  94. package/dist/native-staged-file.d.ts.map +1 -1
  95. package/dist/native-staged-file.js +86 -39
  96. package/dist/owner-dacl.d.ts.map +1 -1
  97. package/dist/owner-dacl.js +10 -4
  98. package/dist/pinned-write-input.d.ts +4 -0
  99. package/dist/pinned-write-input.d.ts.map +1 -0
  100. package/dist/pinned-write-input.js +25 -0
  101. package/dist/pinned-write-mode.d.ts +5 -0
  102. package/dist/pinned-write-mode.d.ts.map +1 -0
  103. package/dist/pinned-write-mode.js +24 -0
  104. package/dist/pinned-write-staged.d.ts +6 -0
  105. package/dist/pinned-write-staged.d.ts.map +1 -0
  106. package/dist/pinned-write-staged.js +187 -0
  107. package/dist/pinned-write-types.d.ts +3 -0
  108. package/dist/pinned-write-types.d.ts.map +1 -1
  109. package/dist/pinned-write.d.ts.map +1 -1
  110. package/dist/pinned-write.js +35 -145
  111. package/dist/private-directory.d.ts.map +1 -1
  112. package/dist/private-directory.js +18 -4
  113. package/dist/private-producer-handoff-sync.d.ts +14 -0
  114. package/dist/private-producer-handoff-sync.d.ts.map +1 -0
  115. package/dist/private-producer-handoff-sync.js +114 -0
  116. package/dist/private-producer-handoff.d.ts +22 -4
  117. package/dist/private-producer-handoff.d.ts.map +1 -1
  118. package/dist/private-producer-handoff.js +140 -77
  119. package/dist/publish-copy-stage.d.ts +2 -1
  120. package/dist/publish-copy-stage.d.ts.map +1 -1
  121. package/dist/publish-copy-stage.js +16 -7
  122. package/dist/publish-file.d.ts.map +1 -1
  123. package/dist/publish-file.js +2 -2
  124. package/dist/replace-file-temp-owner.d.ts +0 -7
  125. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  126. package/dist/replace-file-temp-owner.js +3 -54
  127. package/dist/root-create-input.d.ts +2 -1
  128. package/dist/root-create-input.d.ts.map +1 -1
  129. package/dist/root-create-input.js +13 -4
  130. package/dist/root-directory-creation.d.ts +3 -3
  131. package/dist/root-directory-creation.d.ts.map +1 -1
  132. package/dist/root-directory-creation.js +15 -3
  133. package/dist/root-impl.d.ts.map +1 -1
  134. package/dist/root-impl.js +28 -7
  135. package/dist/root-move-noreplace.d.ts.map +1 -1
  136. package/dist/root-move-noreplace.js +22 -13
  137. package/dist/root-options.d.ts +12 -4
  138. package/dist/root-options.d.ts.map +1 -1
  139. package/dist/root-path-stat.d.ts.map +1 -1
  140. package/dist/root-path-stat.js +59 -7
  141. package/dist/root-write-publication.js +1 -1
  142. package/dist/secret-file.d.ts.map +1 -1
  143. package/dist/secret-file.js +1 -0
  144. package/dist/secure-file-windows.d.ts +6 -0
  145. package/dist/secure-file-windows.d.ts.map +1 -1
  146. package/dist/secure-file-windows.js +34 -117
  147. package/dist/secure-file.js +2 -2
  148. package/dist/sidecar-lock-root.d.ts.map +1 -1
  149. package/dist/sidecar-lock-root.js +2 -1
  150. package/dist/staged-directory.d.ts.map +1 -1
  151. package/dist/staged-directory.js +6 -6
  152. package/dist/staged-file-settlement.d.ts +17 -0
  153. package/dist/staged-file-settlement.d.ts.map +1 -0
  154. package/dist/staged-file-settlement.js +57 -0
  155. package/dist/windows-owner.d.ts.map +1 -1
  156. package/dist/windows-owner.js +2 -1
  157. package/dist/windows-security-bridge.cs +336 -0
  158. package/dist/windows-security-bridge.ps1 +15 -0
  159. package/dist/windows-security-command.d.ts +26 -0
  160. package/dist/windows-security-command.d.ts.map +1 -0
  161. package/dist/windows-security-command.js +363 -0
  162. package/dist/windows-security-facts.d.ts +6 -0
  163. package/dist/windows-security-facts.d.ts.map +1 -0
  164. package/dist/windows-security-facts.js +108 -0
  165. package/docs/advanced.md +3 -1
  166. package/docs/archive.md +61 -37
  167. package/docs/config.md +6 -2
  168. package/docs/contributing.md +44 -4
  169. package/docs/copy.md +2 -0
  170. package/docs/creation.md +128 -0
  171. package/docs/durability.md +24 -0
  172. package/docs/install.md +31 -7
  173. package/docs/migrating-to-0.5.md +15 -6
  174. package/docs/migrating-to-0.6.md +9 -4
  175. package/docs/native-helper.md +22 -9
  176. package/docs/native.md +38 -7
  177. package/docs/permissions.md +37 -14
  178. package/docs/root.md +34 -0
  179. package/docs/secret-file.md +11 -2
  180. package/docs/secure-file.md +9 -4
  181. package/docs/sidecar-lock.md +5 -4
  182. package/docs/staged-file.md +5 -0
  183. package/docs/writing.md +71 -5
  184. package/package.json +18 -15
package/docs/native.md CHANGED
@@ -17,7 +17,7 @@ guarded JavaScript path. Native loading is lazy; installs do not compile Rust,
17
17
  run postinstall code, or fetch binaries at runtime. Seven exact-version optional
18
18
  packages are filtered by OS, CPU, and Linux libc, so an installation receives
19
19
  only its matching prebuilt binding.
20
- Native-only formats and creation-time Windows DACL guarantees fail explicitly
20
+ Native-only formats fail explicitly
21
21
  instead of substituting a weaker implementation.
22
22
 
23
23
  ## The beneath model
@@ -115,6 +115,17 @@ the guarded Node staging/publication boundary. ZIP behavior is unchanged.
115
115
  `maxMetaEntryBytes` bounds bodies before allocation; unsupported global/old
116
116
  metadata and sparse forms fail closed. See [bounded local PAX support](archive.md#bounded-local-pax-support).
117
117
 
118
+ The bundled module also compiles the same zstd and bzip2 codec implementations
119
+ used by native. In `off` or missing-native `auto`, those decoders feed the shared
120
+ TAR parser through fixed 64 KiB windows in one import-free WASM session with a
121
+ 256 MiB linear-memory ceiling. Gzip retains Node's built-in decoder. Complete
122
+ container and TAR admission precedes policy evaluation and guarded publication;
123
+ concatenated compressed members and zstd skippable frames are consumed through
124
+ physical EOF. No runtime command, interpreter, download, or consumer compilation
125
+ is needed for these archive routes. `require` stays strict, and available native
126
+ operation failures do not retry through WASM. Public `inspectTarArchive()` still
127
+ accepts only plain TAR/gzip; ZIP fallback still requires optional JSZip.
128
+
118
129
  Every raw pass receives only TypeScript's resolved `maxEntries`,
119
130
  `maxMetaEntryBytes`, and `maxDecodedBytes`. Shared resolution caps metadata and
120
131
  decoded byte fields at JavaScript's safe-integer maximum and entry counts at
@@ -172,12 +183,26 @@ not bypass the byte limit.
172
183
  does not load the binding. Use asynchronous `sha256File()` for native hashing
173
184
  and cancellation that can respond while JavaScript callbacks run.
174
185
 
175
- Features without a safe JavaScript implementation, including no-clobber
176
- `Root.move()`, zstd/bzip2 TAR, Windows private-directory creation, and
186
+ Features without a safe fallback, including no-clobber
187
+ `Root.move()` and
177
188
  [retained-directory staging](staged-file.md), fail with `helper-unavailable`
178
189
  when native support is absent or off. Staging is currently Linux/macOS only and
179
190
  rejects Windows with `unsupported-platform`.
180
191
 
192
+ Windows raw owner/DACL inspection, private-directory creation, and secure-file
193
+ descriptor inspection can use a package-shipped, readable `.ps1` driver and
194
+ adjacent `.cs` source in `auto` or `off` mode when their binding or capability is
195
+ unavailable. System Windows PowerShell runs the fixed driver with `-File`;
196
+ paths remain data, with no runtime-generated helper script or encoded launcher.
197
+ The [Windows security fallback prerequisites](install.md#windows-security-fallback)
198
+ apply, and unsupported or disallowed command execution fails closed. This route
199
+ preserves raw ACL facts, private DACLs at creation, and descriptor-bound secure
200
+ reads, and emits a path-free `FS_SAFE_NATIVE_FALLBACK` warning once per capability
201
+ per process. Each call adds PowerShell startup and compilation overhead.
202
+ `require` rejects missing capabilities without a command, and an available native operation's
203
+ failure never triggers this fallback. See [Permissions](permissions.md) and
204
+ [Secure file reads](secure-file.md) for error and platform contracts.
205
+
181
206
  The staged-file owner also serves POSIX native pinned writes, including streaming.
182
207
  Unpublished files remain at `0600`; requested modes are applied through the
183
208
  owned file descriptor only after rename and published-entry identity validation.
@@ -219,15 +244,21 @@ rejection, archive filters/limits/modes, exclusive target creation, source and
219
244
  target identity fencing, publication cleanup receipts, and secret/lock policy
220
245
  remain TypeScript-owned. What changes is the syscall strength or availability:
221
246
 
247
+ The table compares underlying mechanisms. On Node, public `Root.open()`,
248
+ `Root.read()`, and `Root.openWritable()` use guarded Node file opens and report
249
+ `containment: "best-effort"` in every native mode. `require` checks availability
250
+ when an operation requests native support; it does not upgrade those results.
251
+ See [Root containment guarantees](security-model.md#containment-guarantees-by-platform).
252
+
222
253
  | Capability | Native path | Guarded JavaScript path |
223
254
  |---|---|---|
224
- | Root-relative opens/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. Linux reports `kernel-atomic`; macOS and Windows report `best-effort`. 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. |
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` on Linux and `best-effort` on 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. |
225
256
  | 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. |
226
- | Zstd/bzip2 TAR | Supported. | Unsupported; typed `helper-unavailable`. |
257
+ | 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. |
227
258
  | 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. |
228
259
  | `rename-noreplace` | Atomic platform no-replace rename. | Unsupported; no emulation by check-then-rename. |
229
- | 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. | Structured .NET owner/DACL inspection remains available to standalone pathname reporting. Secure-file reads fail closed without the descriptor capability. |
230
- | Windows private directory | Creation-time protected DACL. | Unsupported; no weaker pathname-only substitute. |
260
+ | 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 PowerShell/C# bridge 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. |
261
+ | Windows private directory | Creation-time protected DACL. | The packaged PowerShell/C# bridge applies the protected DACL at creation and retains exact handles through identity validation and failure cleanup. Command failures reject. |
231
262
 
232
263
  Use `off` in CI to keep the fallback contract exercised. Use `require` when a
233
264
  deployment depends on the stronger mechanism or a native-only feature; do not
@@ -42,7 +42,7 @@ 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()` does not use those pathname fallbacks on Windows: it requires descriptor-bound native owner/DACL facts for the exact handle it reads.
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 PowerShell/C# bridge in `auto` and `off` modes.
46
46
 
47
47
  ## Advanced Windows ACL helpers
48
48
 
@@ -69,9 +69,10 @@ resolveWindowsUserPrincipal(env);
69
69
  ```
70
70
 
71
71
  The fallback Windows inspector reads the owner and DACL together through one
72
- built-in Windows PowerShell/.NET query. It returns canonical SIDs and numeric
73
- access masks, so Unicode paths and account names do not pass through lossy
74
- console display text. `inspectWindowsAcl()` uses native descriptor facts for
72
+ built-in Windows PowerShell/.NET query. The query addresses its JSON command by
73
+ module name and limits module discovery to PowerShell's bundled system modules.
74
+ It returns canonical SIDs and numeric access masks, so Unicode paths and account
75
+ names do not pass through lossy console display text. `inspectWindowsAcl()` uses native descriptor facts for
75
76
  complete local ACLs with nonzero inherited ACEs (or empty/null DACLs) when the
76
77
  optional Windows binding is available. It applies
77
78
  the same classifier to native facts and the fallback query, returning canonical
@@ -171,10 +172,12 @@ Object-specific and other ACE layouts are not guessed: they are omitted,
171
172
  `complete` becomes false, and their numeric types appear in
172
173
  `unsupportedAceTypes`, allowing a security-sensitive caller to fail closed.
173
174
  Non-Windows systems return `{ status: "unsupported-platform", platform }`.
174
- Windows requires the native binding; if it is unavailable or forced
175
- off, the call throws `FsSafeError("helper-unavailable")`. The existing coarse
176
- `inspectPathPermissions()` API still owns its compatibility fallback and trust
177
- classification.
175
+ Windows prefers the native binding. In native `auto` or `off` mode, a missing
176
+ binding or capability uses the packaged PowerShell/C# bridge with
177
+ the same raw ACE projection. Native `require` rejects either absence with
178
+ `FsSafeError("helper-unavailable")` and starts no command. An available native
179
+ query's failure is terminal. The existing coarse `inspectPathPermissions()` API
180
+ still owns its compatibility fallback and trust classification.
178
181
 
179
182
  ## Private directories
180
183
 
@@ -188,10 +191,11 @@ await createPrivateDirectory(sqliteDirectory);
188
191
  await openSqlite(path.join(sqliteDirectory, "sessions.sqlite"));
189
192
  ```
190
193
 
191
- On Windows with native support, this creates the directory and applies a
194
+ On Windows, this creates the directory and applies a
192
195
  protected owner + LocalSystem + Administrators full-control DACL directly with
193
- an atomic security descriptor; no PowerShell or `icacls` process is launched.
194
- The native operation retains the parent and exact created-directory handles
196
+ an atomic security descriptor. The native route launches no command. When its
197
+ binding or capability is unavailable, native `auto` and `off` modes use the
198
+ packaged PowerShell/C# bridge. Both routes retain the parent and exact created-directory handles
195
199
  through ACL and final pathname validation. If validation fails, it attempts only
196
200
  nonrecursive deletion through the created handle, preserving any pathname
197
201
  replacement. If cleanup also fails, the error retains the original failure and
@@ -216,13 +220,32 @@ also rejects explicit `.` and `..` components, including spellings such as
216
220
  `.\private` and `parent\..\private`, as a compatibility restriction. Simple
217
221
  relative names without these components remain supported.
218
222
 
219
- This API is Windows-only and native-only; it fails closed with
220
- `FsSafeError("helper-unavailable")` on other platforms, when native mode is off,
221
- or when the binding is unavailable. POSIX callers should create private
223
+ This API is Windows-only; it fails closed with `FsSafeError("helper-unavailable")`
224
+ on other platforms. Native `require` also fails if the binding or capability is
225
+ missing and never starts a command. An available native operation's failure is
226
+ terminal. POSIX callers should create private
222
227
  directories through their existing trusted-root creation policy rather than a
223
228
  pathname-only compatibility shim. Existing Windows permission inspection still
224
229
  retains its structured .NET compatibility fallback.
225
230
 
231
+ The raw owner/DACL and private-directory fallbacks each emit one path-free
232
+ `FS_SAFE_NATIVE_FALLBACK` warning per process. PowerShell startup and C#
233
+ compilation add overhead to each call; install the native package for frequent
234
+ operations. These routes run the package's readable, fixed scripts under normal
235
+ system PowerShell policy; see the [Windows security fallback prerequisites](install.md#windows-security-fallback).
236
+ If command support is unavailable, disallowed, or fails, the operation rejects.
237
+ Private-directory creation never falls back to inherited permissions.
238
+ The asynchronous creation command has a 30-second deadline. After a timeout or
239
+ transport failure, fs-safe requests termination and waits at most one further
240
+ second before rejecting and closing its output pipes. The error distinguishes
241
+ observed process exit from an unconfirmed termination attempt. If the OS refuses
242
+ termination, the command can still create the directory after rejection. An
243
+ already-created object retains its protected DACL, but pathname validation and
244
+ owned-handle cleanup may not finish. An error therefore does not prove the
245
+ pathname is absent; a retry can report `EEXIST`. Before retrying or using the
246
+ pathname, establish that the earlier operation stopped and verify any existing
247
+ directory's security. The library does not attempt pathname-based cleanup.
248
+
226
249
  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.
227
250
 
228
251
  ## Types
package/docs/root.md CHANGED
@@ -130,8 +130,32 @@ fs.mkdir(rel, options?) // mkdir -p (creates missing parents)
130
130
  fs.ensureRoot(options?) // accepts "" / "." as the root itself
131
131
  ```
132
132
 
133
+ `mkdir`, `ensureRoot`, `create`, and `createJson` accept `private: true`.
134
+ Missing directories are created with private permissions, and an existing
135
+ requested directory must already be private. Existing ancestors are not
136
+ chmodded or assigned new ACLs. Private files use owner-only POSIX permissions
137
+ or a protected Windows DACL granting access to the current user, System, and
138
+ Administrators. On macOS, private directories and files must also have no ACL;
139
+ creation rejects relevant inheritable parent ACLs, while noninheriting parent
140
+ ACLs remain allowed. A native helper with `inspectDarwinAcl` is required. Native
141
+ `off`, a missing helper, or an older helper without that capability rejects with
142
+ `helper-unavailable` before creating parents or stages. See [creation](creation.md)
143
+ for platform support, synchronous leaf creation, and failure handling.
144
+
145
+ ```ts
146
+ await fs.mkdir("private-data", { private: true });
147
+ await fs.create("private-data/credential", "synthetic credential", { private: true });
148
+ ```
149
+
133
150
  `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`).
134
151
 
152
+ Buffered `create` and `createJson` also accept `atomic?: boolean`. With `true`,
153
+ complete content is staged before exclusive publication even in native-off mode;
154
+ the fallback requires hardlinks. Omitted or `false` keeps the existing buffered
155
+ publication behavior. Streamed creates always stage complete content. The flag
156
+ does not change `durable` or promise stronger containment or crash durability.
157
+ See [atomic creation and settlement](writing.md#atomic-buffered-creation).
158
+
135
159
  `create` also accepts `AsyncIterable<Uint8Array>` with `RootCreateStreamOptions`:
136
160
  the same path, authority, mode, and durability options, plus `maxBytes` and
137
161
  `signal`, without `encoding` or `renameIdentity`. It consumes one chunk at a
@@ -152,6 +176,11 @@ and parent-directory fsync calls. Use it only for reconstructible data: a crash
152
176
  may lose the write or leave the previous file. See [Writing](writing.md#write-options)
153
177
  for platform details.
154
178
 
179
+ `create` and `createJson` additionally accept `durable: "file"` to require file
180
+ synchronization, including propagating `EPERM`. Parent-directory synchronization
181
+ retains its existing best-effort behavior. This option applies to buffered and
182
+ streamed creation and does not select a publication strategy.
183
+
155
184
  `copyIn` accepts a `RootCopySource`: a trusted absolute source path or a file
156
185
  within another Root. The guarded form supplies `root` with only its `open` and
157
186
  `stat` read capabilities, plus `relativePath`:
@@ -300,6 +329,11 @@ the caller, which must check authority before its own later writes.
300
329
 
301
330
  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. 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.
302
331
 
332
+ `move()` snapshots its merged default and per-call mutation policy before
333
+ asynchronous preparation. Later changes to the original policy objects or arrays
334
+ apply to subsequent calls. Use `assertBeforeMutation` for live revocation of an
335
+ in-flight move.
336
+
303
337
  For writes, creates, streams, and copies, parent creation admits the prospective
304
338
  file and each missing directory before creating that directory, including on the
305
339
  Windows native route. An exact deny on an existing parent does not prevent using
@@ -133,6 +133,15 @@ startWebhookVerifier(signingKey);
133
133
 
134
134
  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.
135
135
 
136
+ On POSIX, both native and JavaScript writers verify actual `0o600` permission
137
+ bits through the retained descriptor before writing content. A filesystem that
138
+ reports successful chmod without enforcing those bits fails with
139
+ `insecure-permissions` before any payload is written, including when an explicit
140
+ `dirMode` permits other users to traverse the parent. The requested final `mode`
141
+ is still applied after content writes, including restrictive and special-bit
142
+ overrides. This mode-bit check does not require native ACL inspection; JavaScript
143
+ secret writes remain available on macOS.
144
+
136
145
  Concurrent writes to distinct leaves may share creation of a missing parent.
137
146
  After a parent-creation race, the helper re-inspects the entry and requires a
138
147
  non-symlink directory, then revalidates root/parent guards, containment, and
@@ -270,9 +279,9 @@ await withTimeout(
270
279
 
271
280
  ## Threat model notes
272
281
 
273
- - These helpers protect the secret file from **other processes with the same UID** that respect filesystem permissions. They do not defend against root or against attackers who can read process memory.
282
+ - On POSIX, the default `0600` file and `0700` directory modes restrict group and other access. They do not protect against processes with the same UID, root, attackers who can read process memory, or access granted by additional ACL entries.
274
283
  - Validation failures are tripwires, not authorization. Investigate before clearing a rejected credential file.
275
- - If the destination directory is on a tmpfs that does not honor mode bits, the helpers will set the mode bits but the OS may ignore them. Audit your platform.
284
+ - On POSIX, a file that still reports a mode other than `0600` after initialization is rejected with `insecure-permissions` before payload is written. Matching mode reports alone cannot prove that an arbitrary filesystem actually enforces those permissions.
276
285
 
277
286
  ## See also
278
287
 
@@ -27,9 +27,13 @@ The helper:
27
27
  - enforces `maxBytes` before and after reading
28
28
  - closes the handle on success, error, and timeout
29
29
 
30
- On POSIX, unsafe permissions mean group/world writable, and group/world readable unless `permissions.allowReadableByOthers` is true. On Windows, the helper queries owner, DACL, and locality from the same open descriptor that supplies the bytes. The native query returns the 32-bit volume serial and 64-bit file-index projection used by Node, which must equal Node's bigint descriptor receipt before its ACL facts are trusted. This avoids JavaScript number rounding but does not represent the full 128-bit file identity available on ReFS. Only the current user, LocalSystem, and built-in Administrators are trusted owner classes.
30
+ On POSIX, unsafe permissions mean group/world writable, and group/world readable unless `permissions.allowReadableByOthers` is true. On Windows, the helper queries owner, DACL, and locality from the same open descriptor that supplies the bytes. Both native and system-command queries return the 32-bit volume serial and 64-bit file-index projection used by Node, which must equal Node's bigint descriptor receipt before its ACL facts are trusted. This avoids JavaScript number rounding but does not represent the full 128-bit file identity available on ReFS. Only the current user, LocalSystem, and built-in Administrators are trusted owner classes.
31
31
 
32
- Windows secure reads require the matching current optional native package. A missing or stale helper, fd-to-handle conversion failure, denied `READ_CONTROL`, remote handle, incomplete descriptor, or unsupported ACE form rejects with `permission-unverified` before content is read. A malformed or different handle identity rejects with `path-mismatch`. There is no pathname-command fallback for `readSecureFile()`; the standalone reporting APIs in [`permissions`](permissions.md) retain their documented fallbacks. `permissions.allowInsecure` remains the explicit escape hatch and bypasses the ACL query.
32
+ Windows secure reads prefer the matching optional native package. In native `auto` or `off` mode, a missing binding or descriptor-inspection capability uses a packaged, readable PowerShell script and adjacent C# source to inspect the borrowed file handle. This route requires the [Windows security fallback prerequisites](install.md#windows-security-fallback), including permission to run the scripts under normal system policy. The command does not read file contents or reopen the pathname. Successful inspection waits for the child to exit and its output pipes to close. This emits a path-free `FS_SAFE_NATIVE_FALLBACK` warning once per process for secure reads and adds PowerShell startup and compilation overhead to each inspection.
33
+
34
+ Descriptor commands have a 30-second deadline. After a timeout or transport failure, fs-safe requests termination, waits at most one further second, and then rejects even if process exit or pipe closure remains unconfirmed. It closes its own output pipes and reports the observed exit separately from the termination attempt in the error cause. If the OS refuses termination, the child may retain its independently inherited Windows handle; closing the caller's descriptor cannot retarget that handle. No file bytes are returned from a failed inspection.
35
+
36
+ Native `require` still rejects a missing binding or capability with `permission-unverified`, without starting a command. An available native helper's failure is terminal. On either route, fd-to-handle conversion failure, denied `READ_CONTROL`, a remote handle, incomplete descriptor, unsupported ACE form, or unavailable command support rejects with `permission-unverified` before content is read. A malformed or different handle identity rejects with `path-mismatch`. The standalone reporting APIs in [`permissions`](permissions.md) retain their documented pathname fallbacks. `permissions.allowInsecure` remains the explicit escape hatch and bypasses the ACL query.
33
37
 
34
38
  Descriptor, pathname, and realpath identity checks use bigint stats internally to avoid JavaScript number rounding. The returned `stat` remains a normal Node `Stats` object with numeric fields. A zero Windows device or inode is unverified, never a match: the helper re-inspects that identity once using the same descriptor or pathname, then rejects persistent ambiguity with `path-mismatch`. A definite mismatch rejects immediately; retries retain known identity components and still enforce symlink policy.
35
39
 
@@ -92,13 +96,14 @@ On an actual Windows process with effective `platform: "win32"`, `inject.env` an
92
96
  | `timeout` | `timeoutMs` elapsed while reading. |
93
97
 
94
98
  Windows descriptor-inspection failures are operational `permission-unverified`
95
- errors and refuse the read. The original native exception is retained as
99
+ errors and refuse the read. The original native or descriptor-command exception is retained as
96
100
  `cause`; treat causes as restricted local diagnostic data. No pathname or ACL
97
101
  content is copied into the display message. Test adapters that simulate Windows
98
102
  on another operating system retain the standalone pathname inspector's
99
103
  structured command diagnostics (`ownerError`, `command`, `durationMs`,
100
104
  `timedOut`, `exitCode`, `signal`, and bounded escaped `stderr`). Actual Windows
101
- secure reads do not start those commands. No retries are performed.
105
+ secure reads never invoke the injected pathname inspector: their optional
106
+ command route inspects the borrowed descriptor instead. No retries are performed.
102
107
 
103
108
  ## See also
104
109
 
@@ -295,10 +295,11 @@ Discarding an acquisition observation is not proof that the pathname is absent:
295
295
  another owner may already have created the next record. Every discarded
296
296
  observation consumes the normal retry/deadline budget and requires fresh
297
297
  exclusive creation. It supplies no release, reclaim, or held-lock authority.
298
- If that successor disappears during the recovery metadata probe, the waiter
299
- may discard the probe only with an operation-local receipt for an admitted
300
- regular file with one link, followed by current Root and canonical ancestor
301
- checks. A generic metadata error does not permit this retry, and public
298
+ If that successor disappears or is replaced during the recovery metadata probe,
299
+ the waiter may discard the probe only with an operation-local receipt for an
300
+ admitted regular file with one link. Replacement also requires a single exact
301
+ observation of a different regular file with one link. Current Root and canonical
302
+ ancestor checks must still pass. A generic metadata error does not permit this retry, and public
302
303
  `Root.stat()` still rejects a file that changes during observation.
303
304
  Generic `Root.open()` and held-owner/reclaim reads still reject failed opens.
304
305
  Moving an already-matched pinned descriptor without unlinking it, unknown or
@@ -65,6 +65,11 @@ interface StagedFile extends AsyncDisposable {
65
65
  Strings are UTF-8. `mode` is the requested **published** mode and defaults to
66
66
  `0600`; exact final modes, including `000`, are supported. The unpublished file
67
67
  stays at `0600` throughout preparation and any awaited application checks.
68
+ The retained descriptor's actual mode is checked before writing payload bytes,
69
+ by `assertCurrent()`, and before publication; a successful but ineffective
70
+ `chmod` fails with `insecure-permissions`. Final mode verification after
71
+ publication can fail with a `published` receipt while preserving the completed
72
+ file. These are POSIX mode checks, not ACL or ownership admission.
68
73
  After rename succeeds and the published entry passes identity validation, the
69
74
  owner applies the requested mode through its retained file descriptor. Content
70
75
  was synchronized during preparation; publication always synchronizes the parent.
package/docs/writing.md CHANGED
@@ -6,11 +6,11 @@ half-written replacement appears at the destination. Create-only writes
6
6
  (`create`, `createJson`, and `write` with `overwrite: false`) use sibling-temp
7
7
  staging with an atomic no-replace rename only on backends that provide one —
8
8
  the native binding, which `require` mode guarantees and `auto` mode uses when
9
- the binding loads. The pure-JavaScript fallback has no atomic no-clobber
10
- rename and does not stage: it claims the final name exclusively with `O_EXCL`
11
- and writes content in place, so a concurrent observer can see the new file
12
- before its content is complete. Use `require` mode when that visibility window
13
- matters.
9
+ the binding loads. Ordinary buffered creation in the pure-JavaScript fallback
10
+ claims the final name exclusively with `O_EXCL` and writes content in place, so
11
+ a concurrent observer can see the new file before its content is complete.
12
+ Buffered `create` and `createJson` accept `atomic: true` to stage complete content
13
+ on this fallback too. Streamed creation already stages before publication.
14
14
  `append` and `openWritable` intentionally modify an opened file in place;
15
15
  `move`, `remove`, and `mkdir` mutate directory entries rather than file bytes.
16
16
  Each verb applies the boundary checks appropriate to its operation.
@@ -113,6 +113,14 @@ Native and pure-JavaScript Windows writers honor the option. Replacement writes
113
113
  sync staged content before rename and the final mode through the retained file
114
114
  handle. Directory sync remains best-effort.
115
115
 
116
+ For `create` and `createJson`, `durable: "file"` requires each file sync to succeed,
117
+ including on `EPERM`; it overrides a disabled Root durability default. Parent
118
+ directory synchronization retains the existing best-effort policy. This also
119
+ applies to streamed creation and is independent of publication strategy. Boolean
120
+ durability options keep their existing behavior, including compatibility paths
121
+ that tolerate `EPERM`. A failed file sync before staged publication prevents
122
+ publication; a failure after publication can leave the complete file present.
123
+
116
124
  POSIX modes without read permission, including `0o000` and `0o200`, succeed:
117
125
  final verification uses a descriptor retained by the writer rather than reopening
118
126
  the published file. The requested mode is not relaxed for verification.
@@ -154,6 +162,41 @@ try {
154
162
  }
155
163
  ```
156
164
 
165
+ ### Atomic buffered creation
166
+
167
+ ```ts
168
+ await fs.create("config/seed.json", initial, { atomic: true });
169
+ await fs.createJson("config/settings.json", { enabled: true }, { atomic: true });
170
+ await fs.create("config/flushed.json", initial, { atomic: true, durable: "file" });
171
+ ```
172
+
173
+ `atomic: true` keeps the destination absent until all bytes have been written.
174
+ The native backend uses its no-replace rename; the JavaScript fallback hardlinks
175
+ the completed stage and unlinks its temporary name in the same JavaScript turn.
176
+ The fallback requires hardlink support and fails without publishing partial bytes
177
+ when that mechanism is unavailable. Other processes can briefly observe both
178
+ names. Existing and raced entries are preserved, including dangling symlinks;
179
+ ordinary confinement, type, hardlink, and symlink-policy rejections still apply.
180
+ `assertBeforeMutation` retains its live checks through content writes and publication.
181
+
182
+ Omitted or `false` preserves the existing buffered behavior. The option belongs
183
+ to buffered `create` and `createJson`, not replacement writes or Root defaults.
184
+ Streamed creation has no atomic opt-out. `atomic` changes visibility, not the
185
+ existing `durable` file/directory synchronization policy; it does not turn
186
+ best-effort synchronization into a strict crash-durability guarantee or strengthen
187
+ JavaScript pathname containment.
188
+
189
+ Atomic and streamed creates settle owned cleanup and close operations before
190
+ returning. Failed or unverifiable cleanup is reported rather than silently
191
+ discarded. Errors after publication and incomplete-settlement errors carry the
192
+ existing `StagedFileFailureDetails` publication/cleanup receipts where the writer
193
+ can establish them; native disposal can retain them inside a `SuppressedError`
194
+ cause. Preserve those details when handling errors: a rejection can follow
195
+ complete publication, and an indeterminate link or native rename must preserve names for
196
+ recovery. A cleanup or close failure also retains the original operation failure.
197
+ No later verification, mode, or synchronization failure authorizes deleting an
198
+ already published complete destination. See [receipt meanings](staged-file.md).
199
+
157
200
  ### Streamed creation
158
201
 
159
202
  Pass an `AsyncIterable<Uint8Array>` to `create()` when bytes come from a database,
@@ -202,6 +245,12 @@ forcibly interrupted, so cancellation waits for its pending work and cleanup to
202
245
  settle. Do not mutate a yielded chunk until the next pull. Producer errors retain
203
246
  their original value when cleanup succeeds.
204
247
 
248
+ Streamed creation retains the `signal` and `assertBeforeMutation` callback
249
+ selected when the call starts. Replacing or deleting those options during a
250
+ producer wait does not change the in-flight operation. Abort the original signal
251
+ or update the live authority state checked by the original callback to revoke
252
+ it; the callback continues to receive the original options object as `this`.
253
+
205
254
  An aborted or failed operation can leave created parent directories. If a
206
255
  stage's identity or parent cannot be verified during cleanup, the existing
207
256
  guarded cleanup preserves it. After publication, later verification or cleanup
@@ -268,6 +317,10 @@ await fs.move("incoming/foo.txt", "archive/foo.txt", { overwrite: true });
268
317
 
269
318
  Both `from` and `to` are bounded; `..` in either is rejected.
270
319
 
320
+ Mutation policy is captured at call start; changes to caller-owned denial arrays
321
+ apply to later moves. For live cancellation or revocation, throw from
322
+ `assertBeforeMutation` immediately before dispatch.
323
+
271
324
  The default no-clobber mode requires the native helper. It admits both parent
272
325
  directory descriptors and performs a descriptor-relative no-replace rename, so
273
326
  a competitor that creates the target first is preserved and the source remains
@@ -407,6 +460,19 @@ an atomic check-and-delete syscall. Use OS isolation for that threat model.
407
460
  await fs.mkdir("snapshots/2026/05");
408
461
  ```
409
462
 
463
+ Pass `{ private: true }` to create missing components with private permissions.
464
+ An existing requested directory must already satisfy that policy; fs-safe does
465
+ not repair it or change existing ancestor permissions. Concurrent creators may
466
+ reuse the winner only after it passes the same checks.
467
+
468
+ Buffered, streamed, and JSON `create` calls also accept `private: true`.
469
+ New POSIX directories default to `0700` and files to `0600`; conflicting
470
+ group/world or privilege bits are rejected before creation. Restrictive
471
+ owner-only file modes remain available through `mode`. On Windows, creation
472
+ uses protected ACLs rather than interpreting POSIX mode bits as access rules.
473
+ This does not change `create`'s no-overwrite behavior or select its durability
474
+ policy. See [creation](creation.md) for supported backends and owned descriptors.
475
+
410
476
  ### `fs.ensureRoot()`
411
477
 
412
478
  Treats `""` / `"."` as the root itself. Useful when a generic helper computes a relative directory and might end up at the root.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/fs-safe",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Capability-style filesystem roots for Node.js apps that handle untrusted relative paths.",
5
5
  "keywords": [
6
6
  "filesystem",
@@ -25,6 +25,8 @@
25
25
  },
26
26
  "files": [
27
27
  "dist/archive-parser.wasm",
28
+ "dist/windows-security-bridge.cs",
29
+ "dist/windows-security-bridge.ps1",
28
30
  "dist/**/*.js",
29
31
  "dist/**/*.d.ts",
30
32
  "dist/**/*.d.ts.map",
@@ -144,10 +146,10 @@
144
146
  "test:bun": "bun node_modules/vitest/vitest.mjs run --config scripts/bun-vitest.config.ts",
145
147
  "test:bun:native": "bun scripts/bun-native-proof.mjs && bun node_modules/vitest/vitest.mjs run --config scripts/bun-native-vitest.config.ts",
146
148
  "test:coverage": "vitest run --coverage",
147
- "test:coverage:collect": "pnpm build && vitest run --coverage --coverage.reporter=json --coverage.thresholds.lines=0 --coverage.thresholds.functions=0 --coverage.thresholds.statements=0 --coverage.thresholds.branches=0",
149
+ "test:coverage:collect": "pnpm build && pnpm archive:wasm:allocator-tests && vitest run --coverage --coverage.reporter=json --coverage.thresholds.lines=0 --coverage.thresholds.functions=0 --coverage.thresholds.statements=0 --coverage.thresholds.branches=0",
148
150
  "test:coverage:merge": "node scripts/merge-coverage.mjs",
149
151
  "test:security": "vitest run test/fs-safe.test.ts test/read-boundary-bypass.test.ts test/write-boundary-bypass.test.ts test/additional-boundary-bypass.test.ts test/adversarial-boundary-payloads.test.ts",
150
- "check": "pnpm lint:file-size && pnpm lint:fs-boundary && pnpm build && pnpm docs:check && pnpm test && node scripts/check-pack.mjs",
152
+ "check": "pnpm lint:file-size && pnpm lint:fs-boundary && pnpm build && pnpm archive:wasm:allocator-tests && pnpm docs:check && pnpm test && node scripts/check-pack.mjs",
151
153
  "docs:check": "node scripts/check-doc-examples.mjs",
152
154
  "docs:site": "node scripts/build-docs-site.mjs",
153
155
  "native:build": "pnpm --filter @openclaw/fs-safe-native-build build",
@@ -164,24 +166,25 @@
164
166
  "crabbox:stop": "crabbox stop",
165
167
  "crabbox:warmup": "crabbox warmup",
166
168
  "archive:wasm": "node scripts/build-archive-wasm.mjs",
169
+ "archive:wasm:allocator-tests": "node scripts/build-archive-wasm.mjs --allocator-tests",
167
170
  "archive:producer-smoke": "node scripts/archive-producer-smoke.mjs"
168
171
  },
169
172
  "optionalDependencies": {
170
- "@openclaw/fs-safe-darwin-arm64": "0.15.0",
171
- "@openclaw/fs-safe-darwin-x64": "0.15.0",
172
- "@openclaw/fs-safe-linux-arm64-gnu": "0.15.0",
173
- "@openclaw/fs-safe-linux-arm64-musl": "0.15.0",
174
- "@openclaw/fs-safe-linux-x64-gnu": "0.15.0",
175
- "@openclaw/fs-safe-linux-x64-musl": "0.15.0",
176
- "@openclaw/fs-safe-win32-x64-msvc": "0.15.0",
173
+ "@openclaw/fs-safe-darwin-arm64": "0.16.0",
174
+ "@openclaw/fs-safe-darwin-x64": "0.16.0",
175
+ "@openclaw/fs-safe-linux-arm64-gnu": "0.16.0",
176
+ "@openclaw/fs-safe-linux-arm64-musl": "0.16.0",
177
+ "@openclaw/fs-safe-linux-x64-gnu": "0.16.0",
178
+ "@openclaw/fs-safe-linux-x64-musl": "0.16.0",
179
+ "@openclaw/fs-safe-win32-x64-msvc": "0.16.0",
177
180
  "jszip": "^3.10.2"
178
181
  },
179
182
  "devDependencies": {
180
183
  "@emnapi/runtime": "2.0.0-alpha.5",
181
- "@napi-rs/cli": "3.10.0",
182
- "@types/node": "^26.5.1",
184
+ "@napi-rs/cli": "3.10.3",
185
+ "@types/node": "^26.6.1",
183
186
  "@vitest/coverage-v8": "5.0.1",
184
- "fast-check": "^4.9.0",
187
+ "fast-check": "^4.10.1",
185
188
  "istanbul-lib-coverage": "3.2.2",
186
189
  "istanbul-lib-report": "3.0.1",
187
190
  "istanbul-reports": "3.2.0",
@@ -189,10 +192,10 @@
189
192
  "tar": "7.5.22",
190
193
  "typescript": "^7.0.2",
191
194
  "vite": "8.3.0",
192
- "vitest": "^5.0.0"
195
+ "vitest": "^5.0.1"
193
196
  },
194
197
  "engines": {
195
198
  "node": ">=22"
196
199
  },
197
- "packageManager": "pnpm@11.25.0"
200
+ "packageManager": "pnpm@12.4.2"
198
201
  }