@openclaw/fs-safe 0.18.2 → 0.20.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 (124) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +15 -5
  3. package/dist/advanced.d.ts +2 -0
  4. package/dist/advanced.js +1 -0
  5. package/dist/archive-durability.js +1 -1
  6. package/dist/archive-merge.js +1 -1
  7. package/dist/archive-plan.d.ts +2 -7
  8. package/dist/archive-read.js +9 -18
  9. package/dist/archive-staging.js +3 -1
  10. package/dist/archive-zip-entry.d.ts +11 -11
  11. package/dist/archive-zip-entry.js +3 -35
  12. package/dist/archive-zip-integrity.d.ts +2 -2
  13. package/dist/archive-zip-integrity.js +2 -12
  14. package/dist/archive-zip-loader.d.ts +7 -3
  15. package/dist/archive-zip-loader.js +10 -9
  16. package/dist/archive-zip-preflight.d.ts +2 -1
  17. package/dist/archive-zip-preflight.js +16 -7
  18. package/dist/archive.js +17 -16
  19. package/dist/directory-receipt.js +5 -7
  20. package/dist/effective-uid.js +1 -4
  21. package/dist/errors.d.ts +3 -1
  22. package/dist/errors.js +3 -2
  23. package/dist/file-lock-sync-root-held.d.ts +4 -11
  24. package/dist/file-lock-sync-root-held.js +1 -4
  25. package/dist/file-lock-sync-root-io.d.ts +1 -4
  26. package/dist/file-lock-sync-root.d.ts +2 -4
  27. package/dist/file-store-boundary.d.ts +3 -7
  28. package/dist/file-store-boundary.js +7 -10
  29. package/dist/file-store-prune.js +3 -2
  30. package/dist/file-store.d.ts +4 -7
  31. package/dist/file-store.js +19 -21
  32. package/dist/guest-native-python.js +23 -31
  33. package/dist/guest.js +21 -12
  34. package/dist/json-document-store.d.ts +4 -9
  35. package/dist/json-durable-queue.js +2 -6
  36. package/dist/local-file-access.js +2 -5
  37. package/dist/local-file-descriptor.d.ts +2 -5
  38. package/dist/local-roots.d.ts +2 -7
  39. package/dist/move-path-cleanup.js +4 -4
  40. package/dist/native-binding.d.ts +18 -14
  41. package/dist/native-staged-symlink.d.ts +13 -0
  42. package/dist/native-staged-symlink.js +303 -0
  43. package/dist/owner-dacl-batch-worker.d.ts +1 -0
  44. package/dist/owner-dacl-batch-worker.js +54 -0
  45. package/dist/owner-dacl-batch.d.ts +5 -0
  46. package/dist/owner-dacl-batch.js +64 -0
  47. package/dist/owner-dacl.d.ts +2 -0
  48. package/dist/owner-dacl.js +3 -0
  49. package/dist/path.js +17 -1
  50. package/dist/permission-exec.js +3 -6
  51. package/dist/permissions-public.d.ts +1 -0
  52. package/dist/permissions-public.js +1 -0
  53. package/dist/pinned-mutation-admission.d.ts +0 -1
  54. package/dist/pinned-mutation-shared-route.d.ts +2 -8
  55. package/dist/pinned-open.d.ts +0 -1
  56. package/dist/pinned-open.js +1 -2
  57. package/dist/publish-copy-stage.js +4 -0
  58. package/dist/read-opened-file.d.ts +2 -5
  59. package/dist/regular-file.js +3 -3
  60. package/dist/replace-file-copy-fallback.d.ts +1 -2
  61. package/dist/root-context.js +3 -2
  62. package/dist/root-impl.js +0 -3
  63. package/dist/root-move-noreplace.d.ts +2 -7
  64. package/dist/root-observed-path.d.ts +0 -1
  65. package/dist/root-observed-path.js +0 -3
  66. package/dist/root-path-observation.d.ts +4 -11
  67. package/dist/root-path.js +7 -10
  68. package/dist/root-paths.d.ts +2 -6
  69. package/dist/root-remove-identity.d.ts +1 -3
  70. package/dist/root-walk.js +8 -3
  71. package/dist/root-write-admission.js +1 -6
  72. package/dist/root-write-complete-parent.d.ts +2 -0
  73. package/dist/root-write-complete-parent.js +1 -1
  74. package/dist/safe-path-segment.d.ts +1 -0
  75. package/dist/safe-path-segment.js +8 -2
  76. package/dist/secret-file.d.ts +6 -2
  77. package/dist/secret-file.js +1 -0
  78. package/dist/secure-file-windows.js +1 -5
  79. package/dist/secure-file.js +3 -2
  80. package/dist/sidecar-lock-admission-parser.d.ts +1 -2
  81. package/dist/sidecar-lock-handle.d.ts +2 -8
  82. package/dist/sidecar-lock-policy.d.ts +2 -7
  83. package/dist/sidecar-lock-stale-admission.d.ts +1 -5
  84. package/dist/sidecar-lock.js +5 -3
  85. package/dist/staged-symlink-types.d.ts +49 -0
  86. package/dist/staged-symlink-types.js +1 -0
  87. package/dist/symlink-parents.js +58 -7
  88. package/dist/temp-target.js +4 -2
  89. package/dist/temp-workspace-owner.js +4 -9
  90. package/dist/test-hooks.d.ts +1 -1
  91. package/dist/text-atomic.d.ts +2 -1
  92. package/dist/text-atomic.js +2 -0
  93. package/dist/trash.js +27 -1
  94. package/dist/walk.d.ts +2 -5
  95. package/dist/windows-owner.d.ts +0 -1
  96. package/dist/windows-owner.js +0 -1
  97. package/dist/windows-security-bridge.cs +6 -4
  98. package/dist/windows-security-bridge.ps1 +78 -3
  99. package/dist/windows-security-command.d.ts +8 -0
  100. package/dist/windows-security-command.js +66 -15
  101. package/dist/windows-security-facts.d.ts +4 -0
  102. package/dist/windows-security-facts.js +6 -2
  103. package/docs/advanced.md +3 -2
  104. package/docs/archive.md +10 -0
  105. package/docs/atomic.md +11 -3
  106. package/docs/contributing.md +30 -0
  107. package/docs/copy.md +2 -0
  108. package/docs/file-store.md +5 -0
  109. package/docs/guest.md +7 -1
  110. package/docs/install.md +28 -0
  111. package/docs/native-helper.md +11 -4
  112. package/docs/native.md +45 -1
  113. package/docs/permissions.md +66 -0
  114. package/docs/public-api.md +7 -1
  115. package/docs/root.md +10 -1
  116. package/docs/secret-file.md +10 -0
  117. package/docs/security-model.md +4 -1
  118. package/docs/sidecar-lock.md +2 -0
  119. package/docs/staged-symlink.md +123 -0
  120. package/docs/store.md +3 -1
  121. package/docs/testing.md +28 -0
  122. package/docs/walk.md +12 -0
  123. package/docs/writing.md +21 -0
  124. package/package.json +9 -9
package/docs/testing.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # Testing
2
2
 
3
+ ## Linux openat2 fallback
4
+
5
+ Build the host addon and package first. The test hook is cached with the native
6
+ capability probe; set it before starting the process, rather than changing it
7
+ between tests in one process:
8
+
9
+ ```bash
10
+ pnpm native:build
11
+ pnpm build
12
+ FS_SAFE_TEST_NO_OPENAT2=1 FS_SAFE_NATIVE_MODE=require pnpm test test/linux-openat2-fallback.test.ts test/root-move-noreplace.test.ts test/root-move-native-integration.test.ts test/native-write-containment.test.ts
13
+ ```
14
+
15
+ On Linux, the seccomp harness also exercises the real syscall failure without
16
+ the environment hook. It needs a C compiler and permission to install an
17
+ unprivileged seccomp filter; it affects only its child process:
18
+
19
+ ```bash
20
+ cc test/fixtures/deny-openat2.c -o /tmp/fs-safe-deny-openat2
21
+ /tmp/fs-safe-deny-openat2 ENOSYS node test/fixtures/linux-openat2-fallback.mjs "$PWD/native/fs-safe-native.linux-x64-gnu.node"
22
+ /tmp/fs-safe-deny-openat2 EPERM node test/fixtures/linux-openat2-fallback.mjs "$PWD/native/fs-safe-native.linux-x64-gnu.node"
23
+ ```
24
+
25
+ Use the matching native artifact filename on other Linux architectures/libcs.
26
+ The fixture proves nested moves, collisions, read/write, traversal, symlink and
27
+ hardlink rejection, cached selection, and `helper-unavailable` for strict
28
+ bounded cleanup. Bounded-cleanup success tests require real `openat2`; run the
29
+ full suite with the environment hook unset.
30
+
3
31
  `@openclaw/fs-safe/test-hooks` exposes test-only injection points. Registration
4
32
  is allowed only when `process.env.NODE_ENV === "test"` or
5
33
  `process.env.VITEST === "true"`; registering a non-empty hook set elsewhere
package/docs/walk.md CHANGED
@@ -113,6 +113,18 @@ typed `FsSafeError("too-large")` instead.
113
113
 
114
114
  For followed symlinks, both `kind` and `size` describe the resolved target.
115
115
 
116
+ The caller's starting path retains Root home shorthand: `~` and `~/dir` expand
117
+ the home directory when iteration starts and must resolve inside the Root.
118
+ Home-started walks report actual Root-relative paths, such as `home/dir/file`,
119
+ rather than `~/dir/file`. Use `./~/dir` to start at a literal `~` directory.
120
+ An alias within a home-started path is reported under its admitted canonical
121
+ target; ordinary non-home starting aliases retain their caller-supplied spelling.
122
+
123
+ Entry names remain literal filesystem data, including a directory named `~`
124
+ and its descendants. To reuse an entry path in another Root method without
125
+ home-directory expansion, prefix it with `./`, as in
126
+ `capability.open("./" + entry.relativePath)`.
127
+
116
128
  The default `order: "sorted"` visits each directory's names in lexicographic
117
129
  order before descending depth first. It reads and sorts all names in each
118
130
  visited directory. With `maxEntries`, it prepares small metadata batches capped
package/docs/writing.md CHANGED
@@ -211,6 +211,11 @@ can establish them; native disposal can retain them inside a `SuppressedError`
211
211
  cause. Preserve those details when handling errors: a rejection can follow
212
212
  complete publication, and an indeterminate link or native rename must preserve names for
213
213
  recovery. A cleanup or close failure also retains the original operation failure.
214
+ The JavaScript fallback checks the destination again immediately before attempting
215
+ publication. A collision observed there leaves publication unattempted and cleans
216
+ the owned stage. A later collision or other error from the link call remains
217
+ indeterminate, including `EEXIST`; the error code alone does not prove that the
218
+ filesystem left both names unchanged.
214
219
  No later verification, mode, or synchronization failure authorizes deleting an
215
220
  already published complete destination. See [receipt meanings](staged-file.md).
216
221
 
@@ -301,6 +306,12 @@ type RootWriteJsonOptions = RootWriteOptions & {
301
306
 
302
307
  Open in append mode, write, sync the file handle, and close. Honors `mkdir` for the parent directory and syncs the parent directory when the append creates the file. `durable: false` skips both syncs. Pass `prependNewlineIfNeeded: true` to insert a `\n` if the file does not already end in one.
303
308
 
309
+ `mode` selects the creation mode, defaulting to `0o600` when neither the call nor
310
+ the Root supplies it. On POSIX, the process umask can further restrict that mode;
311
+ for example, `mode: 0o640` with umask `0o077` creates a `0o600` file. Existing
312
+ files are not chmodded, even when an explicit `mode` is supplied. Empty appends
313
+ use the same creation rules.
314
+
304
315
  ```ts
305
316
  await fs.append("logs/today.log", `[${ts}] ${line}\n`);
306
317
  await fs.append("notes/scratch.md", "* new bullet", { prependNewlineIfNeeded: true });
@@ -348,6 +359,11 @@ replacing rename. After dispatch it rechecks both parent identities, so a
348
359
  post-operation rejection can mean the no-replace rename completed. Directory
349
360
  moves continue to require `overwrite: true`.
350
361
 
362
+ Linux without `openat2` uses the [guarded native parent walk](native.md#linux-without-openat2).
363
+ The move still uses `renameat2(RENAME_NOREPLACE)` and preserves collisions;
364
+ parent resolution reports the documented `best-effort` containment class.
365
+ Disabling the addon still makes no-clobber moves unavailable.
366
+
351
367
  Both selected canonical endpoints are admitted inside the retained Root after
352
368
  native parent admission. With `mutationSymlinks: "reject"`, both full operation
353
369
  paths are rechecked after the live mutation-authority callback and before
@@ -522,6 +538,11 @@ destination — there is no atomic-rename step. For exclusive publication of a
522
538
  complete stream, use [`create()`](#streamed-creation). For streamed replacement,
523
539
  the [`atomic`](atomic.md) helpers provide a staged writer.
524
540
 
541
+ For all three write modes, `mode` only selects new-file creation permissions,
542
+ defaulting to `0o600` when neither the call nor the Root supplies it. POSIX
543
+ permissions remain subject to the process umask; existing files are not chmodded.
544
+ The returned numeric `stat` records the admitted descriptor before caller writes.
545
+
525
546
  On POSIX, existing-target opens use `O_NONBLOCK` as an admission safeguard so
526
547
  a no-reader FIFO cannot stall regular-file validation. This does not change
527
548
  ordinary regular-file write semantics. `replace` and `update` remain write-only
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/fs-safe",
3
- "version": "0.18.2",
3
+ "version": "0.20.0",
4
4
  "description": "Capability-style filesystem roots for Node.js apps that handle untrusted relative paths.",
5
5
  "keywords": [
6
6
  "filesystem",
@@ -169,18 +169,18 @@
169
169
  "archive:producer-smoke": "node scripts/archive-producer-smoke.mjs"
170
170
  },
171
171
  "optionalDependencies": {
172
- "@openclaw/fs-safe-darwin-arm64": "0.18.2",
173
- "@openclaw/fs-safe-darwin-x64": "0.18.2",
174
- "@openclaw/fs-safe-linux-arm64-gnu": "0.18.2",
175
- "@openclaw/fs-safe-linux-arm64-musl": "0.18.2",
176
- "@openclaw/fs-safe-linux-x64-gnu": "0.18.2",
177
- "@openclaw/fs-safe-linux-x64-musl": "0.18.2",
178
- "@openclaw/fs-safe-win32-x64-msvc": "0.18.2",
172
+ "@openclaw/fs-safe-darwin-arm64": "0.20.0",
173
+ "@openclaw/fs-safe-darwin-x64": "0.20.0",
174
+ "@openclaw/fs-safe-linux-arm64-gnu": "0.20.0",
175
+ "@openclaw/fs-safe-linux-arm64-musl": "0.20.0",
176
+ "@openclaw/fs-safe-linux-x64-gnu": "0.20.0",
177
+ "@openclaw/fs-safe-linux-x64-musl": "0.20.0",
178
+ "@openclaw/fs-safe-win32-x64-msvc": "0.20.0",
179
179
  "jszip": "^3.10.2"
180
180
  },
181
181
  "devDependencies": {
182
182
  "@emnapi/runtime": "2.0.0-alpha.5",
183
- "@napi-rs/cli": "3.10.3",
183
+ "@napi-rs/cli": "3.10.5",
184
184
  "@types/node": "^26.6.1",
185
185
  "@vitest/coverage-v8": "5.0.1",
186
186
  "fast-check": "^4.10.1",