@openclaw/fs-safe 0.14.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.
- package/CHANGELOG.md +88 -0
- package/README.md +38 -10
- package/dist/advanced.d.ts +2 -1
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +2 -1
- package/dist/archive-kind.d.ts +0 -1
- package/dist/archive-kind.d.ts.map +1 -1
- package/dist/archive-kind.js +5 -17
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +113 -46
- package/dist/archive-parser.wasm +0 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +10 -11
- package/dist/archive-tar-stream.d.ts +3 -0
- package/dist/archive-tar-stream.d.ts.map +1 -1
- package/dist/archive-tar-stream.js +56 -37
- package/dist/archive-tar-wasm.d.ts +16 -4
- package/dist/archive-tar-wasm.d.ts.map +1 -1
- package/dist/archive-tar-wasm.js +134 -34
- package/dist/archive-zip-directory.d.ts +4 -0
- package/dist/archive-zip-directory.d.ts.map +1 -1
- package/dist/archive-zip-directory.js +2 -0
- package/dist/archive-zip-entry.d.ts +6 -2
- package/dist/archive-zip-entry.d.ts.map +1 -1
- package/dist/archive-zip-entry.js +23 -8
- package/dist/archive-zip-integrity.d.ts.map +1 -1
- package/dist/archive-zip-integrity.js +3 -4
- package/dist/archive-zip-loader.d.ts.map +1 -1
- package/dist/archive-zip-loader.js +107 -31
- package/dist/archive-zip-names.d.ts +1 -0
- package/dist/archive-zip-names.d.ts.map +1 -1
- package/dist/archive-zip-names.js +6 -0
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +11 -11
- package/dist/bounded-read-stream.d.ts +0 -1
- package/dist/bounded-read-stream.d.ts.map +1 -1
- package/dist/bounded-read-stream.js +0 -6
- package/dist/clone-metadata.d.ts +1 -0
- package/dist/clone-metadata.d.ts.map +1 -1
- package/dist/clone-metadata.js +6 -2
- package/dist/copy-publication.d.ts +6 -0
- package/dist/copy-publication.d.ts.map +1 -1
- package/dist/copy-publication.js +3 -0
- package/dist/copy-tree-portable.d.ts.map +1 -1
- package/dist/copy-tree-portable.js +44 -24
- package/dist/copy.d.ts.map +1 -1
- package/dist/copy.js +29 -11
- package/dist/create-directory.d.ts +20 -0
- package/dist/create-directory.d.ts.map +1 -0
- package/dist/create-directory.js +130 -0
- package/dist/create-file-async.d.ts +7 -0
- package/dist/create-file-async.d.ts.map +1 -0
- package/dist/create-file-async.js +121 -0
- package/dist/create-file.d.ts +8 -0
- package/dist/create-file.d.ts.map +1 -0
- package/dist/create-file.js +190 -0
- package/dist/create-owned-file.d.ts +8 -0
- package/dist/create-owned-file.d.ts.map +1 -0
- package/dist/create-owned-file.js +16 -0
- package/dist/create.d.ts +4 -0
- package/dist/create.d.ts.map +1 -0
- package/dist/create.js +2 -0
- package/dist/creation-darwin.d.ts +7 -0
- package/dist/creation-darwin.d.ts.map +1 -0
- package/dist/creation-darwin.js +79 -0
- package/dist/creation-file-state.d.ts +19 -0
- package/dist/creation-file-state.d.ts.map +1 -0
- package/dist/creation-file-state.js +118 -0
- package/dist/creation-path.d.ts +21 -0
- package/dist/creation-path.d.ts.map +1 -0
- package/dist/creation-path.js +71 -0
- package/dist/creation-permissions.d.ts +19 -0
- package/dist/creation-permissions.d.ts.map +1 -0
- package/dist/creation-permissions.js +125 -0
- package/dist/directory-durability.d.ts +1 -1
- package/dist/directory-durability.d.ts.map +1 -1
- package/dist/directory-durability.js +22 -80
- package/dist/directory-guard.d.ts +3 -0
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-mode-node.d.ts +2 -0
- package/dist/directory-mode-node.d.ts.map +1 -1
- package/dist/directory-mode-node.js +8 -0
- package/dist/directory-mode-owner.js +5 -5
- package/dist/directory-receipt.d.ts +24 -0
- package/dist/directory-receipt.d.ts.map +1 -0
- package/dist/directory-receipt.js +127 -0
- package/dist/file-cleanup.d.ts +19 -0
- package/dist/file-cleanup.d.ts.map +1 -0
- package/dist/file-cleanup.js +78 -0
- package/dist/file-handle-transfer.d.ts +2 -0
- package/dist/file-handle-transfer.d.ts.map +1 -1
- package/dist/file-handle-transfer.js +57 -2
- package/dist/file-identity.d.ts.map +1 -1
- package/dist/file-identity.js +18 -4
- package/dist/file-lock-sync-admission.d.ts +19 -0
- package/dist/file-lock-sync-admission.d.ts.map +1 -0
- package/dist/file-lock-sync-admission.js +93 -0
- package/dist/file-lock-sync-root-acquire.d.ts +4 -0
- package/dist/file-lock-sync-root-acquire.d.ts.map +1 -0
- package/dist/file-lock-sync-root-acquire.js +370 -0
- package/dist/file-lock-sync-root-arbitration.d.ts +18 -0
- package/dist/file-lock-sync-root-arbitration.d.ts.map +1 -0
- package/dist/file-lock-sync-root-arbitration.js +66 -0
- package/dist/file-lock-sync-root-held.d.ts +34 -0
- package/dist/file-lock-sync-root-held.d.ts.map +1 -0
- package/dist/file-lock-sync-root-held.js +393 -0
- package/dist/file-lock-sync-root-io.d.ts +44 -0
- package/dist/file-lock-sync-root-io.d.ts.map +1 -0
- package/dist/file-lock-sync-root-io.js +209 -0
- package/dist/file-lock-sync-root-mutation.d.ts +17 -0
- package/dist/file-lock-sync-root-mutation.d.ts.map +1 -0
- package/dist/file-lock-sync-root-mutation.js +277 -0
- package/dist/file-lock-sync-root-options.d.ts +20 -0
- package/dist/file-lock-sync-root-options.d.ts.map +1 -0
- package/dist/file-lock-sync-root-options.js +58 -0
- package/dist/file-lock-sync-root-registration.d.ts +2 -0
- package/dist/file-lock-sync-root-registration.d.ts.map +1 -0
- package/dist/file-lock-sync-root-registration.js +90 -0
- package/dist/file-lock-sync-root.d.ts +36 -0
- package/dist/file-lock-sync-root.d.ts.map +1 -0
- package/dist/file-lock-sync-root.js +361 -0
- package/dist/file-lock-sync-stale-admission.d.ts +24 -0
- package/dist/file-lock-sync-stale-admission.d.ts.map +1 -0
- package/dist/file-lock-sync-stale-admission.js +205 -0
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +245 -205
- package/dist/file-observation.d.ts +1 -1
- package/dist/file-observation.d.ts.map +1 -1
- package/dist/file-store-boundary.d.ts +2 -6
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +3 -9
- package/dist/file-store-prune.d.ts.map +1 -1
- package/dist/file-store-prune.js +5 -1
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +5 -8
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +47 -12
- package/dist/guarded-mkdir.d.ts +1 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +36 -7
- package/dist/json-document-store.d.ts.map +1 -1
- package/dist/json-document-store.js +22 -15
- package/dist/json-durable-queue-ownership.d.ts +0 -1
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +0 -6
- package/dist/move-path.js +1 -1
- package/dist/native-binding.d.ts +13 -1
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-fallback-warning.d.ts +4 -0
- package/dist/native-fallback-warning.d.ts.map +1 -0
- package/dist/native-fallback-warning.js +11 -0
- package/dist/native-operations.d.ts +0 -2
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +0 -24
- package/dist/native-parent-admission.d.ts +5 -2
- package/dist/native-parent-admission.d.ts.map +1 -1
- package/dist/native-parent-admission.js +27 -7
- package/dist/native-pinned-write-windows.d.ts +1 -1
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +174 -29
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +26 -14
- package/dist/native-policy-parent-windows.d.ts +14 -0
- package/dist/native-policy-parent-windows.d.ts.map +1 -0
- package/dist/native-policy-parent-windows.js +209 -0
- package/dist/native-rename-outcome.d.ts +4 -0
- package/dist/native-rename-outcome.d.ts.map +1 -0
- package/dist/native-rename-outcome.js +8 -0
- package/dist/native-staged-file.d.ts +3 -2
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +121 -72
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +12 -8
- package/dist/owner-dacl.d.ts.map +1 -1
- package/dist/owner-dacl.js +10 -4
- package/dist/path-prefix.d.ts.map +1 -1
- package/dist/path-prefix.js +30 -8
- package/dist/path-suffix-aliases.d.ts +2 -0
- package/dist/path-suffix-aliases.d.ts.map +1 -1
- package/dist/path-suffix-aliases.js +25 -17
- package/dist/permission-exec.d.ts +2 -0
- package/dist/permission-exec.d.ts.map +1 -1
- package/dist/permission-exec.js +150 -21
- package/dist/permissions-windows.js +1 -1
- package/dist/pinned-mutation-admission.d.ts.map +1 -1
- package/dist/pinned-mutation-admission.js +10 -5
- package/dist/pinned-mutation-observation.d.ts +0 -1
- package/dist/pinned-mutation-observation.d.ts.map +1 -1
- package/dist/pinned-mutation-observation.js +0 -19
- package/dist/pinned-mutation-shared-route.d.ts +1 -0
- package/dist/pinned-mutation-shared-route.d.ts.map +1 -1
- package/dist/pinned-mutation-shared-route.js +1 -1
- package/dist/pinned-write-input.d.ts +4 -0
- package/dist/pinned-write-input.d.ts.map +1 -0
- package/dist/pinned-write-input.js +25 -0
- package/dist/pinned-write-mode.d.ts +5 -0
- package/dist/pinned-write-mode.d.ts.map +1 -0
- package/dist/pinned-write-mode.js +24 -0
- package/dist/pinned-write-staged.d.ts +6 -0
- package/dist/pinned-write-staged.d.ts.map +1 -0
- package/dist/pinned-write-staged.js +187 -0
- package/dist/pinned-write-types.d.ts +5 -0
- package/dist/pinned-write-types.d.ts.map +1 -1
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +35 -145
- package/dist/private-directory.d.ts.map +1 -1
- package/dist/private-directory.js +18 -4
- package/dist/private-producer-handoff-sync.d.ts +14 -0
- package/dist/private-producer-handoff-sync.d.ts.map +1 -0
- package/dist/private-producer-handoff-sync.js +114 -0
- package/dist/private-producer-handoff.d.ts +22 -4
- package/dist/private-producer-handoff.d.ts.map +1 -1
- package/dist/private-producer-handoff.js +140 -77
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +75 -121
- package/dist/publish-copy-stage.d.ts +2 -1
- package/dist/publish-copy-stage.d.ts.map +1 -1
- package/dist/publish-copy-stage.js +16 -7
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +2 -2
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +1 -15
- package/dist/replace-directory.d.ts.map +1 -1
- package/dist/replace-directory.js +256 -18
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +62 -70
- package/dist/replace-file-copy-source.d.ts.map +1 -1
- package/dist/replace-file-copy-source.js +10 -12
- package/dist/replace-file-temp-owner.d.ts +5 -9
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +56 -72
- package/dist/replace-file.js +6 -6
- package/dist/retained-directory-replacement.d.ts +26 -0
- package/dist/retained-directory-replacement.d.ts.map +1 -0
- package/dist/retained-directory-replacement.js +193 -0
- package/dist/root-boundary.d.ts +1 -0
- package/dist/root-boundary.d.ts.map +1 -1
- package/dist/root-boundary.js +4 -0
- package/dist/root-context.d.ts +0 -8
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +0 -3
- package/dist/root-create-input.d.ts +8 -1
- package/dist/root-create-input.d.ts.map +1 -1
- package/dist/root-create-input.js +17 -4
- package/dist/root-directory-creation.d.ts +3 -3
- package/dist/root-directory-creation.d.ts.map +1 -1
- package/dist/root-directory-creation.js +15 -3
- package/dist/root-directory-list.d.ts +1 -0
- package/dist/root-directory-list.d.ts.map +1 -1
- package/dist/root-directory-list.js +1 -0
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +46 -17
- package/dist/root-move-noreplace.d.ts.map +1 -1
- package/dist/root-move-noreplace.js +24 -15
- package/dist/root-options.d.ts +12 -4
- package/dist/root-options.d.ts.map +1 -1
- package/dist/root-path-errors.d.ts +1 -0
- package/dist/root-path-errors.d.ts.map +1 -1
- package/dist/root-path-errors.js +11 -2
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +11 -35
- package/dist/root-path-stat.d.ts.map +1 -1
- package/dist/root-path-stat.js +59 -7
- package/dist/root-path.js +1 -13
- package/dist/root-remove.d.ts +1 -0
- package/dist/root-remove.d.ts.map +1 -1
- package/dist/root-remove.js +4 -0
- package/dist/root-walk.d.ts +1 -1
- package/dist/root-walk.d.ts.map +1 -1
- package/dist/root-walk.js +17 -2
- package/dist/root-write-admission.d.ts +0 -2
- package/dist/root-write-admission.d.ts.map +1 -1
- package/dist/root-write-admission.js +1 -15
- package/dist/root-write-complete-parent.d.ts.map +1 -1
- package/dist/root-write-complete-parent.js +7 -23
- package/dist/root-write-publication.js +1 -1
- package/dist/root-write-verification.d.ts.map +1 -1
- package/dist/root-write-verification.js +29 -42
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +3 -24
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +3 -24
- package/dist/secret-read-policy.d.ts +6 -2
- package/dist/secret-read-policy.d.ts.map +1 -1
- package/dist/secret-read-policy.js +26 -2
- package/dist/secure-file-windows.d.ts +6 -0
- package/dist/secure-file-windows.d.ts.map +1 -1
- package/dist/secure-file-windows.js +34 -117
- package/dist/secure-file.js +2 -2
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +23 -12
- package/dist/sidecar-lock-acquire.d.ts +2 -28
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +288 -199
- package/dist/sidecar-lock-admission-context.d.ts +19 -0
- package/dist/sidecar-lock-admission-context.d.ts.map +1 -0
- package/dist/sidecar-lock-admission-context.js +60 -0
- package/dist/sidecar-lock-admission-parser.d.ts +43 -0
- package/dist/sidecar-lock-admission-parser.d.ts.map +1 -0
- package/dist/sidecar-lock-admission-parser.js +113 -0
- package/dist/sidecar-lock-admission.d.ts +35 -0
- package/dist/sidecar-lock-admission.d.ts.map +1 -0
- package/dist/sidecar-lock-admission.js +7 -0
- package/dist/sidecar-lock-reclaim.d.ts +9 -4
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +80 -25
- package/dist/sidecar-lock-root.d.ts.map +1 -1
- package/dist/sidecar-lock-root.js +2 -1
- package/dist/sidecar-lock-stale-admission.d.ts +39 -0
- package/dist/sidecar-lock-stale-admission.d.ts.map +1 -0
- package/dist/sidecar-lock-stale-admission.js +232 -0
- package/dist/sidecar-lock-target.d.ts +8 -0
- package/dist/sidecar-lock-target.d.ts.map +1 -0
- package/dist/sidecar-lock-target.js +55 -0
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +100 -16
- package/dist/staged-directory.d.ts.map +1 -1
- package/dist/staged-directory.js +6 -6
- package/dist/staged-file-settlement.d.ts +17 -0
- package/dist/staged-file-settlement.d.ts.map +1 -0
- package/dist/staged-file-settlement.js +57 -0
- package/dist/temp-workspace-descriptor.d.ts.map +1 -1
- package/dist/temp-workspace-descriptor.js +9 -27
- package/dist/temp-workspace-owner.d.ts.map +1 -1
- package/dist/temp-workspace-owner.js +8 -8
- package/dist/walk.d.ts +5 -1
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +19 -6
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +4 -3
- package/dist/windows-security-bridge.cs +336 -0
- package/dist/windows-security-bridge.ps1 +15 -0
- package/dist/windows-security-command.d.ts +26 -0
- package/dist/windows-security-command.d.ts.map +1 -0
- package/dist/windows-security-command.js +363 -0
- package/dist/windows-security-facts.d.ts +6 -0
- package/dist/windows-security-facts.d.ts.map +1 -0
- package/dist/windows-security-facts.js +108 -0
- package/docs/advanced.md +4 -2
- package/docs/archive.md +97 -46
- package/docs/atomic.md +85 -8
- package/docs/config.md +6 -2
- package/docs/contributing.md +44 -4
- package/docs/copy.md +37 -0
- package/docs/creation.md +128 -0
- package/docs/durability.md +24 -0
- package/docs/file-store.md +19 -0
- package/docs/install.md +31 -7
- package/docs/json-store.md +5 -0
- package/docs/migrating-to-0.5.md +15 -6
- package/docs/migrating-to-0.6.md +9 -4
- package/docs/native-helper.md +32 -12
- package/docs/native.md +38 -7
- package/docs/output.md +6 -0
- package/docs/path-prefix.md +10 -0
- package/docs/path-suffix-aliases.md +51 -6
- package/docs/permissions.md +50 -14
- package/docs/public-api.md +3 -2
- package/docs/root.md +56 -3
- package/docs/secret-file.md +11 -2
- package/docs/secure-file.md +9 -4
- package/docs/sidecar-lock.md +114 -8
- package/docs/staged-file.md +12 -3
- package/docs/temp.md +20 -3
- package/docs/walk.md +67 -1
- package/docs/writing.md +76 -6
- package/package.json +19 -16
- package/dist/darwin-acl.d.ts +0 -4
- package/dist/darwin-acl.d.ts.map +0 -1
- package/dist/darwin-acl.js +0 -24
package/docs/creation.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Exclusive leaf creation
|
|
2
|
+
|
|
3
|
+
The creation-policy work is tracked in [#482](https://github.com/openclaw/fs-safe/issues/482).
|
|
4
|
+
|
|
5
|
+
Use `createDirectory()`, `createDirectorySync()`, and `createFileSync()` from
|
|
6
|
+
`@openclaw/fs-safe/advanced` when an existing, trusted parent should receive
|
|
7
|
+
one new entry. These operations are exclusive and nonrecursive: an existing
|
|
8
|
+
entry throws `FsSafeError("already-exists")`, and missing parents are not
|
|
9
|
+
created. They do not repair or adopt an existing destination. `Root.mkdir()`
|
|
10
|
+
continues to own recursive, idempotent directory creation within a Root.
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import {
|
|
14
|
+
createDirectorySync,
|
|
15
|
+
createFileSync,
|
|
16
|
+
} from "@openclaw/fs-safe/advanced";
|
|
17
|
+
import fs from "node:fs";
|
|
18
|
+
|
|
19
|
+
createDirectorySync("/trusted/application/new-state", { private: true });
|
|
20
|
+
using file = createFileSync("/trusted/application/new-state/initial.db", {
|
|
21
|
+
private: true,
|
|
22
|
+
});
|
|
23
|
+
fs.fsyncSync(file.fd);
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Both directory variants return `void`; `createFileSync()` returns an empty,
|
|
27
|
+
read/write descriptor owned by `OwnedFileDescriptorSync`, with
|
|
28
|
+
`{ fd, close(), [Symbol.dispose]() }`. Use the
|
|
29
|
+
owner to close it, rather than calling `fs.closeSync()` yourself. Closing and
|
|
30
|
+
disposal are idempotent; a failed close is not retried through a descriptor
|
|
31
|
+
number that might already have been reused. File creation does not write
|
|
32
|
+
payload data or request file or parent-directory synchronization.
|
|
33
|
+
|
|
34
|
+
## Permission options
|
|
35
|
+
|
|
36
|
+
`CreateDirectoryOptions` and `CreateFileOptions` both support `private?: boolean`
|
|
37
|
+
for private creation. `mode?: number` selects permission
|
|
38
|
+
bits; invalid values and private requests with group/world or special bits
|
|
39
|
+
reject before mutation. Restrictive owner-only modes remain restrictive.
|
|
40
|
+
Without `private`, ordinary Node defaults and the process umask apply. Private
|
|
41
|
+
POSIX creation requests `0700` for directories and `0600` for files by default;
|
|
42
|
+
the umask may restrict those permissions further. Existing directory privacy
|
|
43
|
+
checks never broaden permissions.
|
|
44
|
+
|
|
45
|
+
Native private file creation checks the retained descriptor's actual owner and
|
|
46
|
+
permissions before writing payload bytes, after producer and authority callbacks,
|
|
47
|
+
and at publication. A successful `chmod` is insufficient: filesystems that do not
|
|
48
|
+
enforce owner-only permissions reject before payload writes. The requested final
|
|
49
|
+
mode is verified too; a failure after publication preserves the completed file
|
|
50
|
+
and reports its published outcome.
|
|
51
|
+
|
|
52
|
+
On macOS (Darwin), private creation also requires an ACL-free result. The native
|
|
53
|
+
helper must provide `inspectDarwinAcl`; native `off`, a missing helper, or an
|
|
54
|
+
older helper without that capability rejects with `helper-unavailable` before
|
|
55
|
+
creating parents or staging entries. There is no system-command fallback for
|
|
56
|
+
Darwin private creation. Operations without `private: true` keep their existing
|
|
57
|
+
native-mode behavior.
|
|
58
|
+
|
|
59
|
+
Private Darwin directories must retain owner-read or owner-search permission
|
|
60
|
+
after applying the umask so their ACL can be inspected. Modes `0000` and `0200`
|
|
61
|
+
reject with `helper-unavailable` before directory creation. Existing private
|
|
62
|
+
directories with neither permission also reject with `helper-unavailable`;
|
|
63
|
+
permissions are never broadened to inspect them. Modes `0100`, `0300`, and `0400`
|
|
64
|
+
remain supported, as does the default `0700`. Private files with mode `0000`
|
|
65
|
+
remain supported because inspection uses the owned creation descriptor.
|
|
66
|
+
|
|
67
|
+
Before creation, the parent ACL is inspected for entries that could be inherited
|
|
68
|
+
by the new directory or file, as applicable. Relevant inheritable entries reject
|
|
69
|
+
creation. Noninheriting parent ACLs, such as the usual macOS home-directory
|
|
70
|
+
deny-delete entry, do not reject child creation. Created directories and files
|
|
71
|
+
are checked for owner-only permissions and no ACL before admitting the directory
|
|
72
|
+
or allowing payload writes. An existing directory requested with `private: true`
|
|
73
|
+
must also be owned by the current user, have owner-only permissions, and have no
|
|
74
|
+
ACL. These checks never clear an ACL after creation or repair an existing entry.
|
|
75
|
+
|
|
76
|
+
On Windows, mode bits alone do not establish privacy. Private creation uses a
|
|
77
|
+
protected current-user, LocalSystem and Administrators DACL. A private staging
|
|
78
|
+
directory supplies trusted-only inheritable permissions before Node creates
|
|
79
|
+
the file. The original Node descriptor stays pinned while its full Windows
|
|
80
|
+
identity is compared with a security handle before the file DACL is protected.
|
|
81
|
+
An already broadly accessible file is rejected, not repaired. Keep the trusted
|
|
82
|
+
parent ancestry and staging directory ACL protected from untrusted changes;
|
|
83
|
+
post-operation checks do not make pathname-based fallback operations atomic
|
|
84
|
+
against an adversary who can change that namespace.
|
|
85
|
+
|
|
86
|
+
The internal async writer awaits system commands, file opens and closes, and
|
|
87
|
+
security verification. It does not wrap the synchronous creator in a promise.
|
|
88
|
+
Short identity checks and the existing guarded link/unlink critical sections
|
|
89
|
+
remain synchronous.
|
|
90
|
+
|
|
91
|
+
Private Windows file publication retains the same inode and never overwrites
|
|
92
|
+
an existing destination. The current implementation requires hardlinks on the same
|
|
93
|
+
local filesystem. Unsupported filesystems reject with `helper-unavailable`;
|
|
94
|
+
there is no copy-to-destination fallback. On Windows the owner overlaps a
|
|
95
|
+
verified destination descriptor with the creation descriptor before removing
|
|
96
|
+
the temporary name. Requested read-only attributes are finalized through the
|
|
97
|
+
retained destination descriptor after that handoff.
|
|
98
|
+
|
|
99
|
+
On Windows, native `auto` uses available capabilities; native `off` and
|
|
100
|
+
missing-capability `auto` use the packaged system-command security bridge. Native `require`
|
|
101
|
+
rejects unavailable required capabilities instead of starting a command. The
|
|
102
|
+
private-file capability check runs before creating parents or staging entries;
|
|
103
|
+
directory-only operations require only their directory capabilities. The
|
|
104
|
+
[Windows security fallback prerequisites](install.md#windows-security-fallback)
|
|
105
|
+
apply. All command mutations report unconfirmed outcomes when their reply or
|
|
106
|
+
termination cannot establish completion.
|
|
107
|
+
|
|
108
|
+
## Authority and failure outcomes
|
|
109
|
+
|
|
110
|
+
`assertBeforeMutation?: () => void` is a synchronous current-authority check.
|
|
111
|
+
It runs after preparation and immediately before creation or publication;
|
|
112
|
+
thenables reject before mutation. Parent and file identity checks are repeated
|
|
113
|
+
after the callback. Final permission checks, descriptor settlement and cleanup
|
|
114
|
+
retain the operation's cleanup ownership after publication.
|
|
115
|
+
|
|
116
|
+
Failure does not always mean the final path is absent. Private-file errors
|
|
117
|
+
after publication or ambiguous publication preserve the destination and carry
|
|
118
|
+
`details.publication.status` (`published` or `indeterminate`), the target path and
|
|
119
|
+
staging cleanup outcome. Cleanup and close failures retain the original error
|
|
120
|
+
in the cause chain. When `stageDirectory` is present, `cleanup` describes that
|
|
121
|
+
stage's settlement; it does not mean the published destination was removed.
|
|
122
|
+
If a directory is created but its subsequent admission fails, the error records
|
|
123
|
+
its published path and preserves it. A private file whose staging directory
|
|
124
|
+
cannot be admitted reports `not-published` and identifies the preserved stage.
|
|
125
|
+
Observed replacement paths are preserved. Existing
|
|
126
|
+
`createPrivateDirectory()` retains its Windows-only compatibility contract;
|
|
127
|
+
new portable callers should use the ordinary creation operations with
|
|
128
|
+
`private: true`.
|
package/docs/durability.md
CHANGED
|
@@ -69,6 +69,26 @@ descriptor, pathname identity, and canonical path. `assertCurrent()` repeats
|
|
|
69
69
|
those checks. This prevents a pathname replacement from turning a later sync
|
|
70
70
|
into proof for a different directory.
|
|
71
71
|
|
|
72
|
+
Pathname and descriptor checks compare exact bigint device and inode values.
|
|
73
|
+
Each inspection allows one retry for unknown Windows identity components,
|
|
74
|
+
retaining known components and rejecting definite mismatches immediately.
|
|
75
|
+
Persistent unknown identity fails closed with `path-mismatch`, including on an
|
|
76
|
+
otherwise usable directory. A failed preflight identity check never syncs the
|
|
77
|
+
descriptor; a replacement discovered after sync still rejects the operation.
|
|
78
|
+
|
|
79
|
+
`DirectoryReceipt.identity` remains a numeric Node `Stats` object for metadata
|
|
80
|
+
compatibility, projected from the same exact observation as the private
|
|
81
|
+
identity. Library-created receipts and their identity objects retain a
|
|
82
|
+
private exact snapshot; mutating their public fields cannot change the
|
|
83
|
+
directory authorized by that snapshot. Each returned receipt owns a mutable
|
|
84
|
+
numeric metadata copy. Later admissions retain the original metadata snapshot
|
|
85
|
+
even if advisory fields such as mode or timestamps were edited; changed paths
|
|
86
|
+
or identity components reject. Pass the receipt or its original
|
|
87
|
+
identity object through to later operations to retain this evidence. A copied
|
|
88
|
+
or reconstructed numeric identity is accepted only when both components are
|
|
89
|
+
safe integers and, on Windows, nonzero. Rounded or unknown caller identities
|
|
90
|
+
fail with `path-mismatch` rather than authorizing a different directory.
|
|
91
|
+
|
|
72
92
|
Call `close()` in `finally`. Closing is idempotent; using a closed pin fails.
|
|
73
93
|
|
|
74
94
|
These checks intentionally reject a moved or replaced pathname. For one file's
|
|
@@ -94,6 +114,10 @@ accepted.
|
|
|
94
114
|
`expectedExistingIdentity` binds an existing target to an identity observed by
|
|
95
115
|
the caller before a separate permission or policy check. A missing or replaced
|
|
96
116
|
target fails with `FsSafeError("path-mismatch")`.
|
|
117
|
+
Use bigint `dev` and `ino` from `lstat(path, { bigint: true })` or
|
|
118
|
+
[`readDirectoryIdentity()`](directory-identity.md) for caller-owned observations
|
|
119
|
+
that may exceed the numeric safe-integer range. An original library receipt's
|
|
120
|
+
`identity` object also retains its private exact identity for this option.
|
|
97
121
|
|
|
98
122
|
## Exclusive file publication
|
|
99
123
|
|
package/docs/file-store.md
CHANGED
|
@@ -133,6 +133,13 @@ the published entry intact for caller-owned recovery. Ordinary write-only and
|
|
|
133
133
|
mode-000 outputs do not require a readable descriptor when pathname metadata is
|
|
134
134
|
available.
|
|
135
135
|
|
|
136
|
+
If a synchronous write operation and its final temp-descriptor close both fail,
|
|
137
|
+
the store reports them in operation-then-close order in an `AggregateError`.
|
|
138
|
+
This ordering and the original JavaScript thrown value are preserved even when
|
|
139
|
+
that value is `undefined` or otherwise falsy. An unsuccessful best-effort temp
|
|
140
|
+
unlink remains registered for identity-checked process-exit cleanup; it does not
|
|
141
|
+
prevent the close attempt or replace either reportable failure.
|
|
142
|
+
|
|
136
143
|
If an opaque pathname cannot be reopened because of an ACL denial or sharing
|
|
137
144
|
restriction, the synchronous writer intentionally rejects with `path-mismatch`:
|
|
138
145
|
its exact publication identity cannot be verified. There is no equal-content
|
|
@@ -219,6 +226,14 @@ with the same precedence as `write`.
|
|
|
219
226
|
|
|
220
227
|
Per-call overrides for the store-level defaults:
|
|
221
228
|
|
|
229
|
+
Writes capture byte limits, modes, and durability before asynchronous work or
|
|
230
|
+
stream consumption. JSON writes capture those fields and the trailing-newline
|
|
231
|
+
setting before serialization. Later mutation cannot change those captured values.
|
|
232
|
+
Accessors run on the original options object. Ordinary writes retain content
|
|
233
|
+
conversion and byte-limit validation before reading modes and durability.
|
|
234
|
+
The legacy non-private stream `tempPrefix` accessor still runs after staging;
|
|
235
|
+
it does not control the publication policy.
|
|
236
|
+
|
|
222
237
|
```ts
|
|
223
238
|
type FileStoreWriteOptions = {
|
|
224
239
|
durable?: boolean; // store default, otherwise true
|
|
@@ -283,6 +298,10 @@ refreshes are preserved; replacements that are themselves expired remain
|
|
|
283
298
|
eligible. This does not require read permission. The existing best-effort
|
|
284
299
|
external-process race window after dispatch still applies.
|
|
285
300
|
|
|
301
|
+
Empty-directory pruning likewise rechecks that the selected entry is still a
|
|
302
|
+
directory immediately before guarded removal. File and symlink replacements
|
|
303
|
+
are preserved, and a directory that becomes nonempty is left in place.
|
|
304
|
+
|
|
286
305
|
## Difference from `Root`
|
|
287
306
|
|
|
288
307
|
| `FileStore` | `Root` |
|
package/docs/install.md
CHANGED
|
@@ -119,22 +119,46 @@ Use the main entry for the common surface, or the focused subpaths when you want
|
|
|
119
119
|
|
|
120
120
|
## Runtime dependencies
|
|
121
121
|
|
|
122
|
-
`@openclaw/fs-safe` bundles an import-free WASM build of its Rust TAR parser for guarded
|
|
122
|
+
`@openclaw/fs-safe` bundles an import-free WASM build of its Rust TAR parser and zstd/bzip2 codecs for guarded [archive extraction and bounded entry reads](archive.md). Plain TAR, gzip, zstd, and bzip2 work in `off` and missing-native `auto`, including installs with all optional dependencies omitted; gzip uses Node's built-in decoder. These archive fallbacks need no runtime interpreter, download, or consumer compiler. ZIP fallback uses lazily loaded optional `jszip` and reports a missing-dependency error without it. Public subpaths remain safe to import with all optional dependencies omitted, but imports do not prove native availability. Native `require` remains strict, and an available native operation's failure never triggers a WASM retry.
|
|
123
123
|
|
|
124
124
|
There are no peer dependencies. Exact-version optional packages carry the seven
|
|
125
125
|
native targets and npm-compatible OS, CPU, and Linux libc filters install only
|
|
126
|
-
the matching binary. Consumers do not run a
|
|
126
|
+
the matching binary. Consumers do not run a Rust build, download code at
|
|
127
127
|
runtime, or execute a postinstall step. Omitting optional dependencies keeps
|
|
128
|
-
|
|
128
|
+
fallback-capable operations working in `auto` or `off`. Native-only
|
|
129
129
|
features, including strict owned-tree temp cleanup, retained-directory staging,
|
|
130
|
-
atomic `rename-noreplace` (including the default no-clobber `Root.move()`),
|
|
131
|
-
|
|
132
|
-
unavailable. Operations without a safe fallback fail with `helper-unavailable`
|
|
130
|
+
and atomic `rename-noreplace` (including the default no-clobber `Root.move()`),
|
|
131
|
+
remain unavailable. Operations without a safe fallback fail with `helper-unavailable`
|
|
133
132
|
when the matching package is absent, incompatible, or disabled.
|
|
134
133
|
|
|
135
134
|
Upgrading an existing 0.5 consumer? Follow [Migrating to 0.6](migrating-to-0.6.md)
|
|
136
135
|
before deploying with native mode `require` or native-only features.
|
|
137
136
|
|
|
137
|
+
### Windows security fallback
|
|
138
|
+
|
|
139
|
+
Windows raw owner/DACL inspection, private-directory creation, and secure-file
|
|
140
|
+
reads work without the addon in `auto` or `off` mode when system Windows
|
|
141
|
+
PowerShell and its .NET `Add-Type` compilation support are available. The package
|
|
142
|
+
ships a readable, fixed `.ps1` driver and adjacent `.cs` source and invokes the
|
|
143
|
+
driver with Windows PowerShell `-File`. Paths are passed as data. The fallback
|
|
144
|
+
does not generate helper scripts at runtime or use an encoded launcher.
|
|
145
|
+
The driver addresses built-in commands by module name and limits module
|
|
146
|
+
discovery to PowerShell's bundled system modules, avoiding broad command
|
|
147
|
+
discovery scans during each helper startup.
|
|
148
|
+
|
|
149
|
+
Normal PowerShell execution policy and Microsoft Defender policy must permit
|
|
150
|
+
the packaged scripts, including their use of `Add-Type`. The package does not
|
|
151
|
+
bypass restrictions, change policies, or add exclusions. Unsupported or
|
|
152
|
+
disallowed command execution fails closed.
|
|
153
|
+
|
|
154
|
+
The fallback preserves private DACLs at creation and inspects the same open
|
|
155
|
+
handle that supplies secure-file bytes. Each capability emits a path-free
|
|
156
|
+
`FS_SAFE_NATIVE_FALLBACK` warning once per process; each call adds PowerShell
|
|
157
|
+
startup and compilation overhead. Execution or compilation failure also fails
|
|
158
|
+
closed. Native `require` still rejects a missing binding or capability, and an
|
|
159
|
+
available native operation's error never triggers a command retry. See
|
|
160
|
+
[Permissions](permissions.md) and [Secure file reads](secure-file.md).
|
|
161
|
+
|
|
138
162
|
## Native helper policy
|
|
139
163
|
|
|
140
164
|
The platform native binaries provide fd-relative open/link/mkdir primitives,
|
|
@@ -147,7 +171,7 @@ where a safe fallback exists. Native-only operations fail with
|
|
|
147
171
|
import { configureFsSafeNative } from "@openclaw/fs-safe/config";
|
|
148
172
|
|
|
149
173
|
configureFsSafeNative({ mode: "auto" }); // default
|
|
150
|
-
configureFsSafeNative({ mode: "off" }); //
|
|
174
|
+
configureFsSafeNative({ mode: "off" }); // disable the addon; reject native-only operations
|
|
151
175
|
configureFsSafeNative({ mode: "require" }); // fail closed if unavailable
|
|
152
176
|
```
|
|
153
177
|
|
package/docs/json-store.md
CHANGED
|
@@ -85,6 +85,11 @@ For `fileStore(...).json(rel, options)`, `options.durable` overrides the parent
|
|
|
85
85
|
file store's durability, while omission or `undefined` inherits it. Modes,
|
|
86
86
|
identity checks, mutation serialization, and sidecar locking are unchanged.
|
|
87
87
|
|
|
88
|
+
Each `write`, `update`, or `updateOr` invocation captures the retained options'
|
|
89
|
+
`durable` and `trailingNewline` values before queueing, locking, reading, or
|
|
90
|
+
calling the updater. Changes to those options affect later invocations only,
|
|
91
|
+
including when a mutation is waiting behind another operation.
|
|
92
|
+
|
|
88
93
|
The store does **not** validate the parsed value against `T` at runtime — the cast is unchecked. Wrap with a schema (zod/valibot) if the file might be hand-edited or written by another process you don't control.
|
|
89
94
|
|
|
90
95
|
## `read()`
|
package/docs/migrating-to-0.5.md
CHANGED
|
@@ -88,8 +88,10 @@ await extractArchive({
|
|
|
88
88
|
```
|
|
89
89
|
|
|
90
90
|
Returning `"skip"` rejects the archive unless `onFiltered: "skip-entry"` is
|
|
91
|
-
explicit. Zstd and bzip2 TAR
|
|
92
|
-
|
|
91
|
+
explicit. Zstd and bzip2 TAR required native support in version 0.5; current
|
|
92
|
+
versions also use bundled WASM codecs in `off` or missing-native `auto`, through
|
|
93
|
+
the same guarded TAR pipeline. ZIP fallback still requires optional JSZip.
|
|
94
|
+
Catch `ArchiveLimitError` by its code, including
|
|
93
95
|
`archive-entry-path-components-exceeds-limit` for deep implicit-directory
|
|
94
96
|
attacks. See [Archive extraction](archive.md).
|
|
95
97
|
|
|
@@ -167,10 +169,17 @@ See [File locks](sidecar-lock.md), [Secret files](secret-file.md), and
|
|
|
167
169
|
|
|
168
170
|
## 7. Gate native-only features
|
|
169
171
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
172
|
+
Version 0.5 required native support for `createPrivateDirectory()`. Current
|
|
173
|
+
releases also support a packaged PowerShell script in native `auto` and `off`
|
|
174
|
+
modes, retaining its creation-time protected DACL. Check the
|
|
175
|
+
[Windows security fallback prerequisites](install.md#windows-security-fallback)
|
|
176
|
+
before relying on this route. The API remains Windows-only, explicit native
|
|
177
|
+
`require` rejects a missing binding or capability, and native operation failures
|
|
178
|
+
remain terminal.
|
|
179
|
+
`strategy: "rename-noreplace"` remains native-only. Current zstd/bzip2 extraction
|
|
180
|
+
and bounded reads have bundled WASM fallbacks, while explicit native `require`
|
|
181
|
+
remains strict. Test unavailable native-only operations instead of assuming
|
|
182
|
+
installation always succeeds.
|
|
174
183
|
|
|
175
184
|
## 8. Run both behavior families in CI
|
|
176
185
|
|
package/docs/migrating-to-0.6.md
CHANGED
|
@@ -24,10 +24,15 @@ uses native mode `require` or any native-only feature. Version 0.5 kept its
|
|
|
24
24
|
binding in the root tarball; version 0.6 intentionally does not.
|
|
25
25
|
|
|
26
26
|
Omitting optional dependencies remains supported for fallback-capable APIs in
|
|
27
|
-
`auto` mode. It disables native-only features such as
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
`auto` mode. It disables native-only features such as retained-directory staging
|
|
28
|
+
and atomic `rename-noreplace`. Native mode `require`
|
|
29
|
+
reports `helper-unavailable` when the matching package is absent or incompatible.
|
|
30
|
+
Zstd/bzip2 TAR extraction and bounded reads required the binding in version 0.6;
|
|
31
|
+
current versions also support bundled WASM codecs in `auto` and `off` through the
|
|
32
|
+
same guarded TAR pipeline. ZIP fallback still requires optional JSZip.
|
|
33
|
+
Windows private-directory creation required the binding in version 0.6; current
|
|
34
|
+
releases also support [packaged Windows security scripts](install.md#windows-security-fallback)
|
|
35
|
+
in `auto` and `off` mode while preserving the creation-time protected DACL.
|
|
31
36
|
|
|
32
37
|
## Deployment checklist
|
|
33
38
|
|
package/docs/native-helper.md
CHANGED
|
@@ -15,7 +15,7 @@ consumer Rust build.
|
|
|
15
15
|
import { configureFsSafeNative } from "@openclaw/fs-safe/config";
|
|
16
16
|
|
|
17
17
|
configureFsSafeNative({ mode: "auto" }); // default
|
|
18
|
-
configureFsSafeNative({ mode: "off" }); //
|
|
18
|
+
configureFsSafeNative({ mode: "off" }); // disable the addon; reject native-only operations
|
|
19
19
|
configureFsSafeNative({ mode: "require" }); // fail closed when the binding is unavailable
|
|
20
20
|
```
|
|
21
21
|
|
|
@@ -25,14 +25,22 @@ The equivalent environment variables are `FS_SAFE_NATIVE_MODE` and `OPENCLAW_FS_
|
|
|
25
25
|
|
|
26
26
|
| Mode | Behavior |
|
|
27
27
|
|---|---|
|
|
28
|
-
| `auto` | Prefer native primitives when the current platform package loads; otherwise use
|
|
29
|
-
| `off` | Do not load a native package. Use
|
|
28
|
+
| `auto` | Prefer native primitives when the current platform package loads; otherwise use supported fallbacks and reject native-only operations. |
|
|
29
|
+
| `off` | Do not load a native package. Use supported fallbacks and reject native-only operations deterministically. |
|
|
30
30
|
| `require` | Throw `FsSafeError("helper-unavailable")` instead of falling back when an operation needs the native binding and it cannot load. |
|
|
31
31
|
|
|
32
|
-
TAR
|
|
33
|
-
of the same Rust parser
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
Plain TAR, gzip, zstd, and bzip2 extraction and bounded entry reads use a bundled,
|
|
33
|
+
import-free WASM build of the same Rust TAR parser when native support is absent
|
|
34
|
+
or disabled. Zstd/bzip2 codecs are bundled alongside the parser; gzip uses Node's
|
|
35
|
+
built-in decoder. These archive fallbacks require no runtime interpreter or
|
|
36
|
+
download. `off` disables the optional native filesystem helper, not the bundled
|
|
37
|
+
WASM. `auto` prefers native and does not retry a native operation failure through
|
|
38
|
+
WASM; `require` still rejects an unavailable native binding. ZIP fallback still
|
|
39
|
+
requires optional `jszip`. `inspectTarArchive()` remains limited to plain TAR
|
|
40
|
+
and gzip.
|
|
41
|
+
|
|
42
|
+
Windows security operations can use the package's readable PowerShell/C# scripts
|
|
43
|
+
in `auto` and `off`, subject to the [Windows security fallback prerequisites](install.md#windows-security-fallback).
|
|
36
44
|
|
|
37
45
|
On Bun macOS/Linux, the [runtime path adapter](install.md#bun-runtime) uses the
|
|
38
46
|
same Rust addon for system canonicalization in `auto` and `require`. No JIT is
|
|
@@ -95,15 +103,27 @@ clone/copy/hash workers, POSIX canonicalization, and Windows security descriptor
|
|
|
95
103
|
layer owns policy, retries, filters, budgets, modes, cleanup, error
|
|
96
104
|
normalization, and the decision to fall back.
|
|
97
105
|
|
|
98
|
-
- Linux uses `openat2` with `RESOLVE_BENEATH | RESOLVE_NO_MAGICLINKS`, `renameat`, and `renameat2(RENAME_NOREPLACE)`. Owned-tree cleanup enumerates and unlinks through retained directory descriptors and rejects device crossings.
|
|
99
|
-
- 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. Publication uses `renameat` for replacement and `renameatx_np(RENAME_EXCL)` for no-replace; owned-tree cleanup uses descriptor-relative `openat`/`unlinkat`.
|
|
100
|
-
- Windows uses handle-relative `NtCreateFile`, rejects reparse points during root-bounded traversal, uses `FileRenameInfoEx` with replacement selected explicitly by the TypeScript policy layer
|
|
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 enumerates and unlinks through retained directory descriptors and rejects device crossings.
|
|
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
|
+
- 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
|
+
|
|
110
|
+
`replaceDirectoryAtomic()` requires `renameNoReplaceWithIdentity` before it
|
|
111
|
+
creates a missing target parent. On POSIX the dedicated entry point keeps the
|
|
112
|
+
existing pre-dispatch exact receipt fence but dispatches direct-child names
|
|
113
|
+
through the retained parents without another receipt, duplicate, reopen, or
|
|
114
|
+
macOS `F_GETPATH`; the documented final source-name substitution window remains
|
|
115
|
+
there. Deeper names retain guarded parent traversal.
|
|
101
116
|
|
|
102
117
|
Native primitives back create-only and replacing pinned writes, no-clobber
|
|
103
118
|
`Root.move()`, async sidecar creation, guarded publication, archive acceleration,
|
|
104
119
|
and direct Windows ACL operations. Windows secure-file reads require
|
|
105
|
-
descriptor-bound owner/DACL facts
|
|
106
|
-
|
|
120
|
+
descriptor-bound owner/DACL facts. In native `auto` or `off` mode, a missing
|
|
121
|
+
binding or capability can use a packaged PowerShell script that inspects the
|
|
122
|
+
borrowed handle. Raw owner/DACL inspection and private-directory creation also support
|
|
123
|
+
this fallback, subject to the [Windows security fallback prerequisites](install.md#windows-security-fallback).
|
|
124
|
+
Each capability emits a path-free warning once per process and adds PowerShell
|
|
125
|
+
startup and compilation overhead per call. Native `require` rejects missing
|
|
126
|
+
capabilities, and native operation failures remain terminal. No-clobber moves fail with
|
|
107
127
|
`helper-unavailable` when descriptor-relative parent admission or the atomic
|
|
108
128
|
no-replace rename is unavailable; they never use a check followed by a replacing
|
|
109
129
|
rename. Equivalent JavaScript paths remain available for documented
|
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
|
|
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
|
|
176
|
-
`Root.move()
|
|
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
|
|
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 |
|
|
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. |
|
|
230
|
-
| Windows private directory | Creation-time protected DACL. |
|
|
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
|
package/docs/output.md
CHANGED
|
@@ -49,6 +49,12 @@ The requested `path` must name a file. Missing destination parents are created
|
|
|
49
49
|
by the helper because the operation is "produce this output file under the
|
|
50
50
|
root"; callers should choose the filename before calling this API.
|
|
51
51
|
|
|
52
|
+
The helper reads each option once before its first asynchronous operation.
|
|
53
|
+
Changing the options object after invocation does not change the selected
|
|
54
|
+
writer, staging mode, isolation, filename fallback, byte limit, or final mode
|
|
55
|
+
for that write. Workspace writers retain the original options object as their
|
|
56
|
+
callback receiver; sibling writers retain the internal staging receiver.
|
|
57
|
+
|
|
52
58
|
`maxBytes` must be a non-negative safe integer or positive `Infinity`; zero is an active cap and `Infinity` disables it. Invalid values reject before the producer or filesystem staging runs.
|
|
53
59
|
|
|
54
60
|
Use `maxBytes` when the external producer can create arbitrarily large files,
|
package/docs/path-prefix.md
CHANGED
|
@@ -56,6 +56,16 @@ they are collapsed. Native realpath alone does not establish this permission
|
|
|
56
56
|
on every platform. Empty components from repeated or trailing separators do
|
|
57
57
|
not introduce a `.` lookup.
|
|
58
58
|
|
|
59
|
+
Raw component queues of at most 32 entries retain the legacy small-array
|
|
60
|
+
consumption path. After both the initial parse and every symlink expansion, a
|
|
61
|
+
longer queue uses forward cursor bookkeeping instead of moving the unprocessed
|
|
62
|
+
suffix for every component. The fixed small-queue bound limits repeated front
|
|
63
|
+
removal while retaining legacy shift-based consumption for shallow paths. This
|
|
64
|
+
asymptotic bound is not a platform performance result; performance acceptance
|
|
65
|
+
requires separate benchmark evidence. Callers should still apply their own
|
|
66
|
+
input-size limits: the helper is synchronous, retains the raw suffix, and
|
|
67
|
+
performs filesystem work for each non-empty existing component.
|
|
68
|
+
|
|
59
69
|
This is a read-only path observation. It neither pins files nor creates a root
|
|
60
70
|
boundary, authorizes access, or guarantees a consistent snapshot during
|
|
61
71
|
concurrent changes. Results can become stale immediately. Use a guarded Root
|
|
@@ -36,6 +36,7 @@ type ProbePathSuffixAliasesOptions = {
|
|
|
36
36
|
directory: string;
|
|
37
37
|
left: string;
|
|
38
38
|
right: string;
|
|
39
|
+
maxDepth?: number;
|
|
39
40
|
shouldProbeCaseVariants?: (leftNfc: string, rightNfc: string) => boolean;
|
|
40
41
|
};
|
|
41
42
|
|
|
@@ -44,9 +45,11 @@ function probePathSuffixAliasesSync(
|
|
|
44
45
|
): boolean | undefined;
|
|
45
46
|
```
|
|
46
47
|
|
|
47
|
-
The helper reads
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
The helper reads `directory`, rejects non-string values and supplied paths longer
|
|
49
|
+
than 32,768 code units, then rejects NUL-containing inputs. It resolves the path
|
|
50
|
+
to an absolute path and applies the same limit to that result before reading
|
|
51
|
+
`maxDepth`. Each option is read once. Later option getters or the predicate cannot retarget a
|
|
52
|
+
relative directory by changing the working directory. When
|
|
50
53
|
filesystem observations are needed, the directory is canonicalized and its
|
|
51
54
|
identity is checked; an initial directory alias can be followed.
|
|
52
55
|
|
|
@@ -56,26 +59,68 @@ inputs are rejected with `TypeError`. Windows additionally rejects drive-relativ
|
|
|
56
59
|
components, colons, and reserved device names, including device aliases with
|
|
57
60
|
extensions or trailing ignored characters. On POSIX, backslashes and colons are
|
|
58
61
|
ordinary filename characters. The optional predicate must be a function.
|
|
62
|
+
`maxDepth` defaults to `32` and must be a non-negative safe integer; invalid
|
|
63
|
+
values, including `Infinity`, throw `RangeError` before mutation or an
|
|
64
|
+
identical-suffix return. A value of `0` admits no ordinary nonempty suffix.
|
|
59
65
|
|
|
60
66
|
Both suffixes and the predicate are validated before the identical-suffix fast
|
|
61
67
|
path. Identical, valid, within-budget suffixes return `true` without filesystem
|
|
62
68
|
access or a predicate call. This does not prove that the directory exists or that
|
|
63
69
|
the suffix can be created.
|
|
64
70
|
|
|
65
|
-
|
|
71
|
+
The forward-observation allowance never exceeds 32,768, even with a large
|
|
72
|
+
`maxDepth`. A deeper or repeatedly colliding probe can return `undefined` when
|
|
73
|
+
that ceiling is reached. Reverse cleanup still runs outside this allowance.
|
|
66
74
|
|
|
67
|
-
|
|
75
|
+
## Resource budgets
|
|
76
|
+
|
|
77
|
+
The default limits for one call are:
|
|
68
78
|
|
|
69
79
|
| Resource | Limit | On exceeding the limit |
|
|
70
80
|
|---|---|---|
|
|
71
81
|
| Each supplied suffix | 8,192 UTF-16 code units | `RangeError` before mutation |
|
|
72
82
|
| Supplied and resolved directory paths | 32,768 UTF-16 code units each | `RangeError` before mutation |
|
|
73
|
-
| Each suffix's component count | 32 | `RangeError` before mutation |
|
|
83
|
+
| Each suffix's component count | `maxDepth`, default 32 | `RangeError` before mutation |
|
|
74
84
|
| Directory-creation attempts | 128 | `undefined` after cleanup |
|
|
75
85
|
| Successfully created probe directories | 64 | `undefined` after cleanup |
|
|
76
86
|
| Forward filesystem observations | 4,096 | `undefined` after cleanup |
|
|
77
87
|
| Each generated actual path | 32,768 UTF-16 code units | `undefined` after cleanup |
|
|
78
88
|
|
|
89
|
+
### Deeper observations
|
|
90
|
+
|
|
91
|
+
Applications comparing deeper prospective paths can explicitly raise `maxDepth`:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
const prefix = "future/".repeat(32);
|
|
95
|
+
const aliases = probePathSuffixAliasesSync({
|
|
96
|
+
directory: "/trusted/existing-directory",
|
|
97
|
+
left: `${prefix}Report.sqlite`,
|
|
98
|
+
right: `${prefix}report.sqlite`,
|
|
99
|
+
maxDepth: 33,
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The 8,192-code-unit suffix limit and 32,768-code-unit path limits remain fixed.
|
|
104
|
+
Ordinary-component validation, Windows path controls, identity checks, and
|
|
105
|
+
cleanup rules also remain unchanged.
|
|
106
|
+
|
|
107
|
+
Operation budgets grow proportionally from the actual admitted suffix depth,
|
|
108
|
+
not the requested `maxDepth`. For actual depth `D`, let `B = max(32, D)`.
|
|
109
|
+
Directory-creation attempts are limited to `4 × B`, successfully created
|
|
110
|
+
directories to `2 × B`, and forward filesystem observations to `min(32,768, 4 × B²)`.
|
|
111
|
+
The quadratic observation allowance accommodates rechecks of owned ancestors.
|
|
112
|
+
|
|
113
|
+
For example, 33 components allow 132 creation attempts, 66 created directories,
|
|
114
|
+
and 4,356 forward observations; 65 components allow 260, 130, and 16,900.
|
|
115
|
+
Specifying a large `maxDepth` for a short suffix keeps the original budgets.
|
|
116
|
+
The suffix-length limit bounds actual depth to at most 4,096, so every budget
|
|
117
|
+
remains a finite safe integer.
|
|
118
|
+
|
|
119
|
+
Admission does not guarantee a boolean result. Repeated collisions, many
|
|
120
|
+
normalization probes, filesystem limits, or identity failures can exhaust the
|
|
121
|
+
budget or prevent an observation. The helper then returns `undefined` after
|
|
122
|
+
cleanup attempts; callers must preserve their explicit ambiguity policy.
|
|
123
|
+
|
|
79
124
|
Input limits are checked even for identical suffixes. Dynamic budgets count work
|
|
80
125
|
across the whole call, including collision retries; removing a probe does not
|
|
81
126
|
restore its creation budget. Cleanup is still attempted when a forward budget is
|