@openclaw/fs-safe 0.12.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +73 -0
- package/README.md +12 -7
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +32 -11
- package/dist/advanced.d.ts +2 -0
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +2 -0
- package/dist/archive-input.d.ts.map +1 -1
- package/dist/archive-input.js +6 -2
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +15 -2
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +7 -19
- package/dist/archive-plan.js +1 -1
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +18 -13
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +43 -10
- package/dist/archive-tar-inspect.d.ts.map +1 -1
- package/dist/archive-tar-inspect.js +4 -1
- package/dist/archive-zip-admission.d.ts +1 -1
- package/dist/archive-zip-admission.d.ts.map +1 -1
- package/dist/archive-zip-admission.js +2 -2
- package/dist/archive-zip-directory.d.ts +3 -0
- package/dist/archive-zip-directory.d.ts.map +1 -1
- package/dist/archive-zip-directory.js +13 -2
- package/dist/archive-zip-loader.d.ts +3 -2
- package/dist/archive-zip-loader.d.ts.map +1 -1
- package/dist/archive-zip-loader.js +30 -4
- package/dist/archive-zip-manifest.d.ts +5 -0
- package/dist/archive-zip-manifest.d.ts.map +1 -0
- package/dist/archive-zip-manifest.js +22 -0
- package/dist/archive-zip-names.d.ts +6 -1
- package/dist/archive-zip-names.d.ts.map +1 -1
- package/dist/archive-zip-names.js +35 -14
- package/dist/archive-zip-preflight.d.ts.map +1 -1
- package/dist/archive-zip-preflight.js +3 -2
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +9 -4
- package/dist/copy-file-input.d.ts +1 -1
- package/dist/copy-file-input.d.ts.map +1 -1
- package/dist/copy-file-input.js +2 -0
- package/dist/darwin-acl.d.ts +4 -0
- package/dist/darwin-acl.d.ts.map +1 -0
- package/dist/darwin-acl.js +24 -0
- package/dist/deny-mutations.d.ts.map +1 -1
- package/dist/deny-mutations.js +8 -2
- package/dist/directory-durability.d.ts.map +1 -1
- package/dist/directory-durability.js +67 -23
- package/dist/directory-entry-path.d.ts +3 -0
- package/dist/directory-entry-path.d.ts.map +1 -0
- package/dist/directory-entry-path.js +21 -0
- package/dist/directory-guard.d.ts +17 -1
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-guard.js +134 -48
- package/dist/directory-mode-node.d.ts +12 -0
- package/dist/directory-mode-node.d.ts.map +1 -1
- package/dist/directory-mode-node.js +102 -4
- package/dist/effective-uid.d.ts +2 -0
- package/dist/effective-uid.d.ts.map +1 -0
- package/dist/effective-uid.js +25 -0
- package/dist/file-handle-transfer.d.ts.map +1 -1
- package/dist/file-handle-transfer.js +98 -27
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +3 -0
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +36 -6
- package/dist/file-lock.d.ts.map +1 -1
- package/dist/file-lock.js +29 -9
- package/dist/file-observation.d.ts +1 -1
- package/dist/file-observation.d.ts.map +1 -1
- package/dist/file-store-boundary.d.ts +6 -2
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +20 -65
- package/dist/file-store-copy-source.d.ts +5 -0
- package/dist/file-store-copy-source.d.ts.map +1 -0
- package/dist/file-store-copy-source.js +31 -0
- package/dist/file-store-path.d.ts.map +1 -1
- package/dist/file-store-path.js +4 -1
- package/dist/file-store-sync-directory.d.ts +16 -0
- package/dist/file-store-sync-directory.d.ts.map +1 -0
- package/dist/file-store-sync-directory.js +349 -0
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +11 -28
- package/dist/filename.d.ts.map +1 -1
- package/dist/filename.js +65 -20
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +3 -2
- package/dist/guarded-mkdir.d.ts +8 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +176 -27
- package/dist/guest.d.ts.map +1 -1
- package/dist/guest.js +4 -1
- package/dist/home-dir.d.ts.map +1 -1
- package/dist/home-dir.js +73 -10
- package/dist/install-path.d.ts.map +1 -1
- package/dist/install-path.js +54 -19
- package/dist/json-durable-queue-ownership.d.ts +6 -0
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +89 -42
- package/dist/json-durable-queue-paths.d.ts +11 -0
- package/dist/json-durable-queue-paths.d.ts.map +1 -0
- package/dist/json-durable-queue-paths.js +42 -0
- package/dist/json-durable-queue-read.d.ts.map +1 -1
- package/dist/json-durable-queue-read.js +2 -0
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +50 -58
- package/dist/json-store.d.ts.map +1 -1
- package/dist/json-store.js +5 -1
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +54 -22
- package/dist/local-file-access.d.ts.map +1 -1
- package/dist/local-file-access.js +4 -0
- package/dist/local-file-descriptor.d.ts +17 -0
- package/dist/local-file-descriptor.d.ts.map +1 -0
- package/dist/local-file-descriptor.js +84 -0
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +35 -7
- package/dist/move-path-cleanup.d.ts +2 -0
- package/dist/move-path-cleanup.d.ts.map +1 -1
- package/dist/move-path-cleanup.js +44 -18
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +31 -6
- package/dist/native-binding.d.ts +17 -0
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-binding.js +8 -1
- package/dist/native-directory-observation.d.ts +17 -0
- package/dist/native-directory-observation.d.ts.map +1 -0
- package/dist/native-directory-observation.js +37 -0
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +6 -4
- package/dist/native-parent-admission.d.ts +32 -0
- package/dist/native-parent-admission.d.ts.map +1 -0
- package/dist/native-parent-admission.js +127 -0
- package/dist/native-pinned-write-windows.d.ts +0 -1
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +8 -9
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +314 -42
- package/dist/native-staged-file.d.ts +4 -4
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +20 -10
- package/dist/native.d.ts +1 -1
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +7 -2
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +15 -5
- package/dist/overwrite-file-handle.d.ts.map +1 -1
- package/dist/overwrite-file-handle.js +5 -1
- package/dist/owner-dacl.d.ts.map +1 -1
- package/dist/owner-dacl.js +2 -0
- package/dist/path-policy.d.ts.map +1 -1
- package/dist/path-policy.js +7 -2
- package/dist/path-prefix.d.ts +7 -0
- package/dist/path-prefix.d.ts.map +1 -0
- package/dist/path-prefix.js +82 -0
- package/dist/path-scope-lexical.d.ts.map +1 -1
- package/dist/path-scope-lexical.js +18 -8
- package/dist/path-segment-route.d.ts +7 -0
- package/dist/path-segment-route.d.ts.map +1 -0
- package/dist/path-segment-route.js +24 -0
- package/dist/path-suffix-aliases.d.ts +10 -0
- package/dist/path-suffix-aliases.d.ts.map +1 -0
- package/dist/path-suffix-aliases.js +386 -0
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +16 -5
- package/dist/permissions-windows.d.ts.map +1 -1
- package/dist/permissions-windows.js +14 -3
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +37 -8
- package/dist/pinned-mutation-admission.d.ts +25 -0
- package/dist/pinned-mutation-admission.d.ts.map +1 -0
- package/dist/pinned-mutation-admission.js +425 -0
- package/dist/pinned-mutation-observation.d.ts +34 -0
- package/dist/pinned-mutation-observation.d.ts.map +1 -0
- package/dist/pinned-mutation-observation.js +142 -0
- package/dist/pinned-mutation-shared-route.d.ts +24 -0
- package/dist/pinned-mutation-shared-route.d.ts.map +1 -0
- package/dist/pinned-mutation-shared-route.js +70 -0
- package/dist/pinned-open.d.ts +6 -0
- package/dist/pinned-open.d.ts.map +1 -1
- package/dist/pinned-open.js +28 -10
- package/dist/pinned-write-types.d.ts +75 -0
- package/dist/pinned-write-types.d.ts.map +1 -0
- package/dist/pinned-write-types.js +1 -0
- package/dist/pinned-write.d.ts +7 -33
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +151 -17
- package/dist/private-directory.d.ts.map +1 -1
- package/dist/private-directory.js +2 -0
- package/dist/private-producer-handoff.d.ts +16 -0
- package/dist/private-producer-handoff.d.ts.map +1 -0
- package/dist/private-producer-handoff.js +272 -0
- package/dist/private-temp-workspace.d.ts +2 -39
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +183 -77
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +38 -32
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +56 -45
- package/dist/replace-directory.d.ts.map +1 -1
- package/dist/replace-directory.js +12 -5
- package/dist/replace-file-temp-owner.d.ts +1 -0
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +12 -1
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +24 -24
- package/dist/root-boundary.d.ts +4 -0
- package/dist/root-boundary.d.ts.map +1 -1
- package/dist/root-boundary.js +6 -1
- package/dist/root-context.d.ts +12 -1
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +57 -11
- package/dist/root-directory-creation.d.ts +13 -0
- package/dist/root-directory-creation.d.ts.map +1 -0
- package/dist/root-directory-creation.js +212 -0
- package/dist/root-directory-list.d.ts +16 -4
- package/dist/root-directory-list.d.ts.map +1 -1
- package/dist/root-directory-list.js +180 -39
- package/dist/root-directory.d.ts +23 -0
- package/dist/root-directory.d.ts.map +1 -0
- package/dist/root-directory.js +141 -0
- package/dist/root-errors.d.ts.map +1 -1
- package/dist/root-errors.js +12 -1
- package/dist/root-file-final-admission.d.ts +21 -0
- package/dist/root-file-final-admission.d.ts.map +1 -0
- package/dist/root-file-final-admission.js +83 -0
- package/dist/root-file.d.ts.map +1 -1
- package/dist/root-file.js +103 -24
- package/dist/root-impl.d.ts +1 -0
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +289 -317
- package/dist/root-move-noreplace.d.ts +14 -0
- package/dist/root-move-noreplace.d.ts.map +1 -0
- package/dist/root-move-noreplace.js +202 -0
- package/dist/root-observed-path.d.ts +9 -0
- package/dist/root-observed-path.d.ts.map +1 -0
- package/dist/root-observed-path.js +95 -0
- package/dist/root-path-errors.d.ts +12 -0
- package/dist/root-path-errors.d.ts.map +1 -0
- package/dist/root-path-errors.js +13 -0
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +53 -20
- package/dist/root-path-observation.d.ts +63 -0
- package/dist/root-path-observation.d.ts.map +1 -0
- package/dist/root-path-observation.js +180 -0
- package/dist/root-path-stat.d.ts +5 -0
- package/dist/root-path-stat.d.ts.map +1 -0
- package/dist/root-path-stat.js +101 -0
- package/dist/root-path-symlink.d.ts.map +1 -1
- package/dist/root-path-symlink.js +23 -4
- package/dist/root-path.d.ts +11 -0
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +215 -53
- package/dist/root-paths-lexical.d.ts +9 -0
- package/dist/root-paths-lexical.d.ts.map +1 -0
- package/dist/root-paths-lexical.js +22 -0
- package/dist/root-paths.d.ts +3 -25
- package/dist/root-paths.d.ts.map +1 -1
- package/dist/root-paths.js +77 -154
- package/dist/root-read-admission.d.ts +26 -0
- package/dist/root-read-admission.d.ts.map +1 -0
- package/dist/root-read-admission.js +94 -0
- package/dist/root-remove-identity.d.ts +15 -0
- package/dist/root-remove-identity.d.ts.map +1 -0
- package/dist/root-remove-identity.js +89 -0
- package/dist/root-remove-receipt.d.ts +17 -0
- package/dist/root-remove-receipt.d.ts.map +1 -0
- package/dist/root-remove-receipt.js +37 -0
- package/dist/root-remove.d.ts +2 -1
- package/dist/root-remove.d.ts.map +1 -1
- package/dist/root-remove.js +172 -10
- package/dist/root-write-admission.d.ts +68 -0
- package/dist/root-write-admission.d.ts.map +1 -0
- package/dist/root-write-admission.js +322 -0
- package/dist/root-write-compatibility.d.ts +13 -0
- package/dist/root-write-compatibility.d.ts.map +1 -0
- package/dist/root-write-compatibility.js +74 -0
- package/dist/root-write-complete-parent.d.ts +42 -0
- package/dist/root-write-complete-parent.d.ts.map +1 -0
- package/dist/root-write-complete-parent.js +195 -0
- package/dist/root-write-publication.d.ts +24 -0
- package/dist/root-write-publication.d.ts.map +1 -0
- package/dist/root-write-publication.js +85 -0
- package/dist/root-write-verification.d.ts.map +1 -1
- package/dist/root-write-verification.js +6 -4
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +62 -24
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +18 -13
- package/dist/secret-read-policy.d.ts.map +1 -1
- package/dist/secret-read-policy.js +5 -1
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +92 -7
- package/dist/secure-temp-dir.d.ts +6 -3
- package/dist/secure-temp-dir.d.ts.map +1 -1
- package/dist/secure-temp-dir.js +121 -101
- package/dist/secure-temp-repair.d.ts +35 -0
- package/dist/secure-temp-repair.d.ts.map +1 -0
- package/dist/secure-temp-repair.js +106 -0
- package/dist/sibling-staged-file.d.ts +3 -2
- package/dist/sibling-staged-file.d.ts.map +1 -1
- package/dist/sibling-staged-file.js +179 -55
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +27 -13
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +44 -14
- package/dist/sidecar-lock-root.d.ts.map +1 -1
- package/dist/sidecar-lock-root.js +4 -2
- package/dist/staged-directory.d.ts +14 -5
- package/dist/staged-directory.d.ts.map +1 -1
- package/dist/staged-directory.js +54 -3
- package/dist/standalone-publication-path.d.ts +2 -0
- package/dist/standalone-publication-path.d.ts.map +1 -0
- package/dist/standalone-publication-path.js +6 -0
- package/dist/stat-observation.d.ts +11 -0
- package/dist/stat-observation.d.ts.map +1 -0
- package/dist/stat-observation.js +64 -0
- package/dist/strict-file-identity.d.ts.map +1 -1
- package/dist/strict-file-identity.js +39 -2
- package/dist/symlink-parents.d.ts.map +1 -1
- package/dist/symlink-parents.js +7 -2
- package/dist/temp-target.d.ts +2 -0
- package/dist/temp-target.d.ts.map +1 -1
- package/dist/temp-target.js +130 -3
- package/dist/temp-workspace-admission.d.ts +22 -0
- package/dist/temp-workspace-admission.d.ts.map +1 -0
- package/dist/temp-workspace-admission.js +374 -0
- package/dist/temp-workspace-child-admission.d.ts +13 -0
- package/dist/temp-workspace-child-admission.d.ts.map +1 -0
- package/dist/temp-workspace-child-admission.js +95 -0
- package/dist/temp-workspace-descriptor.d.ts +48 -0
- package/dist/temp-workspace-descriptor.d.ts.map +1 -0
- package/dist/temp-workspace-descriptor.js +363 -0
- package/dist/temp-workspace-identity.d.ts +15 -0
- package/dist/temp-workspace-identity.d.ts.map +1 -0
- package/dist/temp-workspace-identity.js +41 -0
- package/dist/temp-workspace-owner.d.ts +7 -6
- package/dist/temp-workspace-owner.d.ts.map +1 -1
- package/dist/temp-workspace-owner.js +121 -61
- package/dist/temp-workspace-permissions.d.ts +4 -0
- package/dist/temp-workspace-permissions.d.ts.map +1 -0
- package/dist/temp-workspace-permissions.js +32 -0
- package/dist/temp-workspace-types.d.ts +40 -0
- package/dist/temp-workspace-types.d.ts.map +1 -0
- package/dist/temp-workspace-types.js +1 -0
- package/dist/temp.d.ts +1 -1
- package/dist/temp.d.ts.map +1 -1
- package/dist/temp.js +1 -1
- package/dist/test-hooks.d.ts +7 -0
- package/dist/test-hooks.d.ts.map +1 -1
- package/dist/text-atomic.d.ts.map +1 -1
- package/dist/text-atomic.js +3 -1
- package/dist/trash.d.ts.map +1 -1
- package/dist/trash.js +54 -8
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +9 -6
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +5 -0
- package/dist/windows-path-alias.d.ts +39 -0
- package/dist/windows-path-alias.d.ts.map +1 -0
- package/dist/windows-path-alias.js +153 -0
- package/docs/advanced.md +25 -0
- package/docs/archive.md +23 -2
- package/docs/atomic.md +27 -3
- package/docs/copy.md +3 -0
- package/docs/durability.md +14 -0
- package/docs/errors.md +14 -6
- package/docs/file-store.md +24 -3
- package/docs/filename.md +14 -7
- package/docs/install-path.md +2 -2
- package/docs/install.md +8 -7
- package/docs/json-store.md +5 -1
- package/docs/json.md +11 -0
- package/docs/mutation-policy-proof.md +65 -0
- package/docs/native-helper.md +41 -11
- package/docs/native.md +32 -8
- package/docs/output.md +33 -11
- package/docs/path-prefix.md +64 -0
- package/docs/path-suffix-aliases.md +159 -0
- package/docs/path.md +11 -0
- package/docs/private-file-store.md +14 -0
- package/docs/public-api.md +9 -0
- package/docs/quickstart.md +1 -1
- package/docs/reading.md +8 -1
- package/docs/root.md +18 -3
- package/docs/secret-file.md +3 -0
- package/docs/secure-file.md +6 -2
- package/docs/security-model.md +75 -8
- package/docs/sidecar-lock.md +28 -1
- package/docs/store.md +5 -1
- package/docs/temp.md +260 -36
- package/docs/test-hooks.md +14 -0
- package/docs/writing.md +38 -8
- package/package.json +10 -10
package/docs/install-path.md
CHANGED
|
@@ -23,7 +23,7 @@ function resolveSafeInstallDir(params: {
|
|
|
23
23
|
}): { ok: true; path: string } | { ok: false; error: string };
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
Computes the absolute install directory for `id` under `baseDir`, after running `id` through `nameEncoder` (`safeDirName` by default). Verifies the result stays inside `baseDir` — anything that would escape returns `{ ok: false, error: invalidNameMessage }`.
|
|
26
|
+
Computes the absolute install directory for `id` under `baseDir`, after running `id` through `nameEncoder` (`safeDirName` by default). Verifies the result stays inside `baseDir` — anything that would escape returns `{ ok: false, error: invalidNameMessage }`. On Windows, contained alternate-stream and filesystem-namespace aliases are rejected with the same result.
|
|
27
27
|
|
|
28
28
|
```ts
|
|
29
29
|
const r = resolveSafeInstallDir({
|
|
@@ -64,7 +64,7 @@ function assertCanonicalPathWithinBase(params: {
|
|
|
64
64
|
}): Promise<void>;
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
Throws if the candidate resolves outside `baseDir` after `realpath`. The `boundaryLabel` is included in the error message ("Invalid path: must stay within {boundaryLabel}").
|
|
67
|
+
Throws if the candidate resolves outside `baseDir` after `realpath`. On Windows it also throws for alternate-stream or filesystem-namespace aliases in the base, candidate, or canonical path. The `boundaryLabel` is included in the containment-shaped error message ("Invalid path: must stay within {boundaryLabel}").
|
|
68
68
|
|
|
69
69
|
```ts
|
|
70
70
|
await assertCanonicalPathWithinBase({
|
package/docs/install.md
CHANGED
|
@@ -127,9 +127,10 @@ the matching binary. Consumers do not run a native build, download code at
|
|
|
127
127
|
runtime, or execute a postinstall step. Omitting optional dependencies keeps
|
|
128
128
|
non-archive 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
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
atomic `rename-noreplace` (including the default no-clobber `Root.move()`),
|
|
131
|
+
zstd/bzip2 TAR handling, and Windows private-directory creation, remain
|
|
132
|
+
unavailable. Operations without a safe fallback fail with `helper-unavailable`
|
|
133
|
+
when the matching package is absent, incompatible, or disabled.
|
|
133
134
|
|
|
134
135
|
Upgrading an existing 0.5 consumer? Follow [Migrating to 0.6](migrating-to-0.6.md)
|
|
135
136
|
before deploying with native mode `require` or native-only features.
|
|
@@ -138,15 +139,15 @@ before deploying with native mode `require` or native-only features.
|
|
|
138
139
|
|
|
139
140
|
The platform native binaries provide fd-relative open/link/mkdir primitives,
|
|
140
141
|
atomic no-replace rename, and file identity checks. The default is `auto`: use
|
|
141
|
-
the matching binary when it loads, otherwise
|
|
142
|
-
|
|
143
|
-
|
|
142
|
+
the matching binary when it loads, otherwise use the guarded JavaScript path
|
|
143
|
+
where a safe fallback exists. Native-only operations fail with
|
|
144
|
+
`helper-unavailable`.
|
|
144
145
|
|
|
145
146
|
```ts
|
|
146
147
|
import { configureFsSafeNative } from "@openclaw/fs-safe/config";
|
|
147
148
|
|
|
148
149
|
configureFsSafeNative({ mode: "auto" }); // default
|
|
149
|
-
configureFsSafeNative({ mode: "off" }); // guarded JavaScript only
|
|
150
|
+
configureFsSafeNative({ mode: "off" }); // guarded JavaScript; reject native-only operations
|
|
150
151
|
configureFsSafeNative({ mode: "require" }); // fail closed if unavailable
|
|
151
152
|
```
|
|
152
153
|
|
package/docs/json-store.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# JSON store
|
|
2
2
|
|
|
3
|
-
`jsonStore` is exported from `@openclaw/fs-safe/store`. It is the
|
|
3
|
+
`jsonStore` is exported from `@openclaw/fs-safe/store`. It is the single-path
|
|
4
4
|
convenience wrapper for `fileStore(...).json(...)`: a small read-modify-write
|
|
5
5
|
handle around a single JSON file. It bakes in atomic writes, explicit fallback
|
|
6
6
|
reads, and optional cross-process locking via
|
|
@@ -71,6 +71,10 @@ type JsonStore<T> = {
|
|
|
71
71
|
|
|
72
72
|
`jsonStore({ filePath })` resolves `rootDir = dirname(filePath)` and calls
|
|
73
73
|
`fileStore({ rootDir, private: true }).json(basename(filePath), options)`.
|
|
74
|
+
On Windows, the factory rejects NTFS alternate-stream and directory-index
|
|
75
|
+
namespace spellings before preparing its private parent. An ordinary
|
|
76
|
+
drive-relative path is anchored at entry and `store.filePath` exposes the
|
|
77
|
+
resulting absolute path; ordinary colon-bearing POSIX paths remain valid.
|
|
74
78
|
|
|
75
79
|
`durable: false` keeps sibling-temp replace/rename behavior but skips the
|
|
76
80
|
temp-file and parent-directory `fsync` calls. Use it only for reconstructible
|
package/docs/json.md
CHANGED
|
@@ -38,6 +38,17 @@ Use `readJson` when missing-or-malformed is a programmer error you want to surfa
|
|
|
38
38
|
|
|
39
39
|
`JsonFileReadError` carries `cause` so you can inspect whether the underlying failure was an `ENOENT`, a `SyntaxError`, or something else.
|
|
40
40
|
|
|
41
|
+
On Windows, filesystem path inputs reject NTFS alternate-stream and
|
|
42
|
+
directory-index namespace spellings such as `file:stream` and
|
|
43
|
+
`dir::$INDEX_ALLOCATION`; ordinary colon-bearing POSIX names remain valid.
|
|
44
|
+
Standalone JSON writers preserve their released support for an ordinary
|
|
45
|
+
drive-relative destination by anchoring its leading drive designator at entry
|
|
46
|
+
without normalizing the remaining suffix. Any additional colon is still
|
|
47
|
+
rejected before filesystem access.
|
|
48
|
+
Strict standalone readers retain `JsonFileReadError` and expose the
|
|
49
|
+
`invalid-path` rejection as its cause, lenient `tryReadJson*` calls return
|
|
50
|
+
`null`, and root-bounded readers report an `open`/`validation` failure.
|
|
51
|
+
|
|
41
52
|
## Reading
|
|
42
53
|
|
|
43
54
|
### `readJson<T>(filePath, options?)`
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Hosted mutation-policy proof
|
|
2
|
+
|
|
3
|
+
The `mutation policy public behavior proof` workflow builds the exact event-head
|
|
4
|
+
package and host addon on Node 24 Linux, macOS, and Windows. Each case executes in
|
|
5
|
+
a fresh process, uses a private temporary fixture, and emits only a bounded,
|
|
6
|
+
canonical receipt. POSIX workers require both real and effective non-root UIDs.
|
|
7
|
+
|
|
8
|
+
Receipt schema `fs-safe-mutation-policy-proof-v2` deliberately does not describe
|
|
9
|
+
final directory emptiness as a mutation-dispatch count. Root replacement records
|
|
10
|
+
the unchanged contents of the original and replacement parent; denied redirect
|
|
11
|
+
records rejection and unchanged denied/displaced parents. The existing JS mkdir
|
|
12
|
+
counters name their exact child or next component. None counts native syscalls
|
|
13
|
+
or excludes transient mutations that leave no final trace.
|
|
14
|
+
|
|
15
|
+
## Additional hosted cases
|
|
16
|
+
|
|
17
|
+
Each POSIX write/create/copy worker, under both native-off and native-require,
|
|
18
|
+
performs an eligible success control, an exact deeper-parent denial after an
|
|
19
|
+
earlier parent is created, a denied redirect after preflight, and a stale-parent
|
|
20
|
+
replacement at the public pre-mkdir authority boundary. Exact directory listings
|
|
21
|
+
and sentinels verify that denied/current/displaced parents contain no prohibited
|
|
22
|
+
child, target, or stage. Copy sources must retain their original bytes.
|
|
23
|
+
|
|
24
|
+
Redirect injection uses the existing public `@openclaw/fs-safe/test-hooks` subpath
|
|
25
|
+
from `dist`, not a mocked native binding. Only these isolated workers enable
|
|
26
|
+
`NODE_ENV=test`. The exact target and single hook visit are required. The hook is
|
|
27
|
+
after policy preflight but before parent admission; it must not be described as
|
|
28
|
+
a post-parent-admission hook. Stale replacement instead uses the public authority
|
|
29
|
+
callback after child-create policy admission, with an existing sentinel parent
|
|
30
|
+
and a still-missing child. Both implementation files that enforce the following
|
|
31
|
+
freshness check are hash-bound. No unchecked callback ordinal chooses a fault.
|
|
32
|
+
|
|
33
|
+
Representative POSIX `Root.write` workers refuse authority before the first mkdir,
|
|
34
|
+
after one parent has been created and before the next mkdir, before staging, and
|
|
35
|
+
immediately before publication. Epoch selection uses actual fixture state. The
|
|
36
|
+
publication refusal requires a real single-link, caller-owned mode-0600 stage
|
|
37
|
+
containing the complete payload while the destination still contains its original
|
|
38
|
+
sentinel. The same rejection object must escape, no callback may follow refusal,
|
|
39
|
+
and the owned stage must be gone before fixture teardown.
|
|
40
|
+
|
|
41
|
+
Windows workers exercise buffer `Root.write` through a stable final-file symlink,
|
|
42
|
+
then refuse before staging a newly created placeholder, before publishing over a
|
|
43
|
+
new placeholder, and before publishing to an existing symlink-selected destination.
|
|
44
|
+
They verify alias binding, destination preservation, observed placeholder/stage
|
|
45
|
+
states, and cleanup before fixture teardown. The default native-off and explicit
|
|
46
|
+
`verify-content-with-lock` native-require configurations both select the existing
|
|
47
|
+
Windows JS buffer writer. The compatibility route is expected not to load the
|
|
48
|
+
addon; the receipt does not mislabel this as native publication or evidence that
|
|
49
|
+
content-verification fallback or lock contention was exercised.
|
|
50
|
+
|
|
51
|
+
## Bounds and interpretation
|
|
52
|
+
|
|
53
|
+
The receipt remains below 32 KiB, each worker has a 15-second process timeout and
|
|
54
|
+
4-KiB stdout limit, and the proof step has a six-minute outer timeout. Worker I/O
|
|
55
|
+
and cleanup retain their existing deadlines. The canonical pending/failed receipt
|
|
56
|
+
is preserved if setup, a worker, provenance validation, or emission fails.
|
|
57
|
+
Source, built modules, the public test seam, harness helper, contract tests, and
|
|
58
|
+
host addon are hashed and checked again after workers.
|
|
59
|
+
|
|
60
|
+
These are deterministic representative observations, not an exhaustive race proof
|
|
61
|
+
or syscall audit. Existing hash-bound internal tests remain complementary for
|
|
62
|
+
other awaited-admission, receipt-refresh, and observation-failure interleavings.
|
|
63
|
+
Windows root replacement and POSIX-specific pinned routes are not claimed on
|
|
64
|
+
Windows. Hosted CI and exact artifact inspection are required before relying on
|
|
65
|
+
new receipts; the proof supplies no performance release clearance.
|
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" }); // guarded JavaScript only
|
|
18
|
+
configureFsSafeNative({ mode: "off" }); // guarded JavaScript; reject native-only operations
|
|
19
19
|
configureFsSafeNative({ mode: "require" }); // fail closed when the binding is unavailable
|
|
20
20
|
```
|
|
21
21
|
|
|
@@ -25,8 +25,8 @@ 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
|
|
29
|
-
| `off` | Do not load a native package. Use
|
|
28
|
+
| `auto` | Prefer native primitives when the current platform package loads; otherwise use guarded JavaScript where a safe fallback exists and reject native-only operations. |
|
|
29
|
+
| `off` | Do not load a native package. Use guarded JavaScript where safe 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
32
|
TAR/gzip in the guarded JavaScript path uses a bundled, import-free WASM build
|
|
@@ -43,6 +43,19 @@ when admitting a temp workspace. Containment and identity checks stay intact.
|
|
|
43
43
|
|
|
44
44
|
Configure the mode once during startup. Loading is lazy and cached; changing from `auto` to `require` after a failed load changes failure policy but does not repeatedly probe the binary.
|
|
45
45
|
|
|
46
|
+
Native-created descriptors retain their originating native close operation through
|
|
47
|
+
normal and error cleanup, including later mode changes. Node-created roots and
|
|
48
|
+
directory handles keep Node's close operation, and borrowed handles keep their
|
|
49
|
+
caller-owned lifetime. This preserves Node worker-thread descriptor tracking
|
|
50
|
+
without unmanaged-descriptor warnings. A helper missing native close support is
|
|
51
|
+
unavailable before descriptor allocation.
|
|
52
|
+
|
|
53
|
+
Close retained native resources and let in-flight operations finish before
|
|
54
|
+
forcibly terminating a worker. Native-created descriptors are not registered
|
|
55
|
+
with Node's automatic worker-exit cleanup; `Worker.terminate()` can leave them
|
|
56
|
+
open until process exit. The native close operation handles explicit cleanup,
|
|
57
|
+
not forced worker termination.
|
|
58
|
+
|
|
46
59
|
[`tempWorkspace()` and its scoped/sync variants](temp.md#private-temp-workspaces)
|
|
47
60
|
remain available in every mode. Their default compatible cleanup uses guarded
|
|
48
61
|
JavaScript quarantine when owned native tree removal is unavailable.
|
|
@@ -62,6 +75,20 @@ change the mode policy of existing fallback-capable APIs.
|
|
|
62
75
|
|
|
63
76
|
## Native boundary
|
|
64
77
|
|
|
78
|
+
The internal Darwin descriptor ACL inspector requires its matching native
|
|
79
|
+
capability in both `auto` and `require`; `off`, a missing package, or an older
|
|
80
|
+
binding without `inspectDarwinAcl` rejects with `helper-unavailable`. Inspection
|
|
81
|
+
failure or malformed facts reject with `permission-unverified`; there is no
|
|
82
|
+
mode-bit or pathname fallback for this capability. Clone admission uses a fused
|
|
83
|
+
descriptor-bound metadata and ACL observation, then compares immutable receipts
|
|
84
|
+
with fresh no-follow pathname identity fences; pathnames never authorize ACL
|
|
85
|
+
state. The payload ACL-clear readback is part of that fused observation. Once
|
|
86
|
+
a clone payload exists, normalization and verification failures become terminal
|
|
87
|
+
`EIO` errors (with the underlying status and detail retained), not capability
|
|
88
|
+
signals that permit an ordinary-copy retry. Checked cleanup cannot undo that
|
|
89
|
+
terminal classification.
|
|
90
|
+
This addition does not change other APIs' native-mode or permission contracts.
|
|
91
|
+
|
|
65
92
|
The native layer exposes policy-free filesystem mechanisms: beneath-root
|
|
66
93
|
open/mkdir/link, replace and no-replace rename, identity reads, archive decode/execution,
|
|
67
94
|
clone/copy/hash workers, POSIX canonicalization, and Windows security descriptor calls. The TypeScript
|
|
@@ -70,14 +97,17 @@ normalization, and the decision to fall back.
|
|
|
70
97
|
|
|
71
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.
|
|
72
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`.
|
|
73
|
-
- Windows uses handle-relative `NtCreateFile`, rejects reparse points during root-bounded traversal, uses `FileRenameInfoEx` with replacement selected explicitly by the TypeScript policy layer, and deletes owned trees 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.
|
|
74
|
-
|
|
75
|
-
Native primitives back create-only and replacing pinned writes,
|
|
76
|
-
guarded publication, archive acceleration,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
100
|
+
- Windows uses handle-relative `NtCreateFile`, rejects reparse points during root-bounded traversal, uses `FileRenameInfoEx` with replacement selected explicitly by the TypeScript policy layer, and deletes owned trees 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.
|
|
101
|
+
|
|
102
|
+
Native primitives back create-only and replacing pinned writes, no-clobber
|
|
103
|
+
`Root.move()`, async sidecar creation, guarded publication, archive acceleration,
|
|
104
|
+
and direct Windows ACL operations. Windows secure-file reads require
|
|
105
|
+
descriptor-bound owner/DACL facts from the current helper; they do not use the
|
|
106
|
+
standalone pathname inspector's command fallback. No-clobber moves fail with
|
|
107
|
+
`helper-unavailable` when descriptor-relative parent admission or the atomic
|
|
108
|
+
no-replace rename is unavailable; they never use a check followed by a replacing
|
|
109
|
+
rename. Equivalent JavaScript paths remain available for documented
|
|
110
|
+
fallback-capable features. See [Native architecture](native.md#javascript-fallback-guarantees-and-delta)
|
|
81
111
|
for the exact difference.
|
|
82
112
|
|
|
83
113
|
The guarded JavaScript mutation path is detection-based, not containment-atomic.
|
package/docs/native.md
CHANGED
|
@@ -50,7 +50,20 @@ whether a path, archive entry, mode, owner, or cleanup policy is acceptable.
|
|
|
50
50
|
race-atomic. macOS uses `renameatx_np(RENAME_EXCL)` and permits
|
|
51
51
|
`fclonefileat` in an owned, non-shared parent. The clone is normalized inside
|
|
52
52
|
a private staging directory: flags, ACLs, extended attributes, and broad mode
|
|
53
|
-
bits are cleared before no-replace publication.
|
|
53
|
+
bits are cleared before no-replace publication. Clone admission obtains mode,
|
|
54
|
+
owner, exact identity, flags, and ACL state together from the retained descriptor;
|
|
55
|
+
immutable receipts are compared across the private staging operation and against
|
|
56
|
+
fresh no-follow pathname identity reads. Any extended entry on the target parent
|
|
57
|
+
or private staging directory is rejected before cloning bytes. The payload's
|
|
58
|
+
cleared ACL and normalized descriptor facts are verified before publication and
|
|
59
|
+
again, with a fresh published-name identity fence, before its descriptor is
|
|
60
|
+
returned. Unsupported admission before payload
|
|
61
|
+
creation or an unsupported clone syscall may still select the documented
|
|
62
|
+
ordinary-copy path. After the clone creates bytes, normalization and security
|
|
63
|
+
verification failures report terminal `EIO`, retaining the original error
|
|
64
|
+
detail; successful cleanup does not make them eligible for ordinary-copy retry.
|
|
65
|
+
Cleanup failures remain secondary diagnostics, and already-terminal publication
|
|
66
|
+
errors such as `EEXIST` retain their status.
|
|
54
67
|
- Windows uses handle-relative `NtCreateFile` with `OBJ_DONT_REPARSE` and
|
|
55
68
|
`FILE_OPEN_REPARSE_POINT`, then explicitly rejects reparse points. Rename and
|
|
56
69
|
hardlink operations stay rooted in already-open handles. Owner/DACL reads
|
|
@@ -62,6 +75,16 @@ whether a path, archive entry, mode, owner, or cleanup policy is acceptable.
|
|
|
62
75
|
missing or partial exports fail with `ENOTSUP` instead of trying a raw HANDLE
|
|
63
76
|
or add-on CRT descriptor namespace.
|
|
64
77
|
|
|
78
|
+
The internal macOS `inspectDarwinAcl(fd)` capability reports `absent`, `empty`,
|
|
79
|
+
or `present` for the opened object's extended ACL. It synchronously owns a
|
|
80
|
+
close-on-exec duplicate for inspection, leaves the caller's descriptor and file
|
|
81
|
+
position alone, and never reopens a pathname. Darwin's `acl_get_entry` returns
|
|
82
|
+
zero for an entry; end-of-list is accepted only for the first entry of a valid,
|
|
83
|
+
privately owned empty ACL. Unsupported, malformed, and failed inspection is not
|
|
84
|
+
reported as absence. These facts do not classify individual ACE permissions,
|
|
85
|
+
prove volume ownership enforcement, or add ACL enforcement to private writers
|
|
86
|
+
and secure readers outside the clone path.
|
|
87
|
+
|
|
65
88
|
## Archives
|
|
66
89
|
|
|
67
90
|
Native ZIP and TAR entry reads retain a private, unpooled input Buffer in
|
|
@@ -141,18 +164,19 @@ not bypass the byte limit.
|
|
|
141
164
|
|
|
142
165
|
| Mode | Native loading | Fallback |
|
|
143
166
|
|---|---|---|
|
|
144
|
-
| `auto` | Try once, cache the result | Use guarded JavaScript when
|
|
167
|
+
| `auto` | Try once, cache the result | Use guarded JavaScript when safe; reject native-only operations |
|
|
145
168
|
| `require` | Try once, cache the result | Throw `FsSafeError("helper-unavailable")` |
|
|
146
|
-
| `off` | Never attempt a binding load |
|
|
169
|
+
| `off` | Never attempt a binding load | Use guarded JavaScript when safe; reject native-only operations |
|
|
147
170
|
|
|
148
171
|
`sha256FileSync()` is a synchronous Node implementation in all three modes and
|
|
149
172
|
does not load the binding. Use asynchronous `sha256File()` for native hashing
|
|
150
173
|
and cancellation that can respond while JavaScript callbacks run.
|
|
151
174
|
|
|
152
|
-
Features without a safe JavaScript implementation, including
|
|
153
|
-
Windows private-directory creation, and
|
|
154
|
-
fail with `helper-unavailable`
|
|
155
|
-
is currently Linux/macOS only and
|
|
175
|
+
Features without a safe JavaScript implementation, including no-clobber
|
|
176
|
+
`Root.move()`, zstd/bzip2 TAR, Windows private-directory creation, and
|
|
177
|
+
[retained-directory staging](staged-file.md), fail with `helper-unavailable`
|
|
178
|
+
when native support is absent or off. Staging is currently Linux/macOS only and
|
|
179
|
+
rejects Windows with `unsupported-platform`.
|
|
156
180
|
|
|
157
181
|
The staged-file owner also serves POSIX native pinned writes, including streaming.
|
|
158
182
|
Unpublished files remain at `0600`; requested modes are applied through the
|
|
@@ -189,7 +213,7 @@ remain TypeScript-owned. What changes is the syscall strength or availability:
|
|
|
189
213
|
|
|
190
214
|
| Capability | Native path | Guarded JavaScript path |
|
|
191
215
|
|---|---|---|
|
|
192
|
-
| 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. 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. A same-privilege peer can replace a writable parent after a guard assertion but before Node resolves
|
|
216
|
+
| 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. |
|
|
193
217
|
| 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. |
|
|
194
218
|
| Zstd/bzip2 TAR | Supported. | Unsupported; typed `helper-unavailable`. |
|
|
195
219
|
| 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. |
|
package/docs/output.md
CHANGED
|
@@ -69,13 +69,23 @@ when needed as described above. Guarded temporary files used only inside
|
|
|
69
69
|
fs-safe have independent names so their length does not grow with the
|
|
70
70
|
destination basename.
|
|
71
71
|
|
|
72
|
+
On Windows, `rootDir` and every target parent reject NTFS alternate-stream and
|
|
73
|
+
directory-index namespace spellings such as `file:stream` and
|
|
74
|
+
`dir::$INDEX_ALLOCATION`. A colon in only the requested basename still follows
|
|
75
|
+
the documented portable filename sanitization above instead of being treated
|
|
76
|
+
as a raw stream path. Ordinary colon-bearing POSIX roots and parents remain
|
|
77
|
+
valid.
|
|
78
|
+
|
|
72
79
|
## Choosing a staging mode
|
|
73
80
|
|
|
74
81
|
`staging: "workspace"` is the default. The producer writes in private temp
|
|
75
82
|
storage, then fs-safe copies through the guarded root boundary. Choose it when
|
|
76
83
|
the temp and destination filesystems may differ, or when an externally produced
|
|
77
84
|
partial file must never appear in the destination directory. The final target
|
|
78
|
-
still appears only after guarded finalization.
|
|
85
|
+
still appears only after guarded finalization. Its internal `tempFile()` does
|
|
86
|
+
not expose `cleanupSafety` through this API and uses compatible cleanup, with
|
|
87
|
+
the final check-to-pathname-recursive-removal gap documented in
|
|
88
|
+
[`tempFile`](temp.md#tempfile).
|
|
79
89
|
|
|
80
90
|
By default, `staging: "sibling"` gives the producer a randomized temp path in
|
|
81
91
|
the target directory. Choose it only when that directory itself is the approved writable
|
|
@@ -104,19 +114,31 @@ absent file path inside a private child workspace under the target parent, on
|
|
|
104
114
|
the target filesystem. Directory cleanup ownership is captured before the
|
|
105
115
|
callback. A callback exception triggers owned workspace cleanup, including
|
|
106
116
|
partial output, subject to directory identity checks and I/O failures.
|
|
107
|
-
After success,
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
117
|
+
After success, an available native helper uses guarded no-replace `Root.move`.
|
|
118
|
+
With native mode off, the helper opens and identity-fences the completed regular
|
|
119
|
+
file, creates the randomized sibling through an atomic no-clobber hard link,
|
|
120
|
+
verifies its temporary two-link state, then removes the private name. Windows
|
|
121
|
+
first transfers the pin to an independently verified sibling descriptor so the
|
|
122
|
+
source name can disappear before workspace cleanup, including on runtimes with
|
|
123
|
+
legacy deletion behavior. If that unlink clears the producer's read-only
|
|
124
|
+
attribute, the retained descriptor restores it and the helper verifies mode and
|
|
125
|
+
identity before continuing. This keeps the handoff zero-copy while preserving the
|
|
126
|
+
ordinary retained-descriptor, mode,
|
|
127
|
+
file-sync, and final-rename lifecycle. Escaping symlinks still fail with
|
|
128
|
+
`path-alias`; filesystems without hard-link support fail with
|
|
129
|
+
`helper-unavailable`.
|
|
112
130
|
|
|
113
131
|
Exact bigint parent and workspace identities are rechecked before moving
|
|
114
132
|
output to the sibling path to reject observed replacements. Cleanup uses the
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
133
|
+
compatible [`withTempFile` ownership contract](temp.md#withtempfile); this
|
|
134
|
+
output option does not expose `cleanupSafety: "require-bounded"`. A moved or
|
|
135
|
+
replaced parent or workspace can leave artifacts, and a workspace substituted
|
|
136
|
+
in the final check-to-pathname-recursive-removal gap can redirect traversal.
|
|
137
|
+
The option does not promise cleanup through a retained directory after a
|
|
138
|
+
rename. The existing Windows and JavaScript pathname-guard limitations remain,
|
|
139
|
+
with no additional permissions or durability guarantee. Native-off publication
|
|
140
|
+
is supported only where hard links are available. See the
|
|
141
|
+
[producer-isolation contract](temp.md#sibling-temp-writes)
|
|
120
142
|
for cleanup and pathname-race details. The option affects only `staging: "sibling"`;
|
|
121
143
|
with `staging: "workspace"`, it is redundant and harmless because the producer
|
|
122
144
|
already uses a private workspace. Omitting it leaves both staging defaults
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Resolving an existing path prefix
|
|
2
|
+
|
|
3
|
+
`resolvePathPrefixSync()` follows filesystem path components until the first
|
|
4
|
+
missing entry. It returns a canonical existing prefix and the unprocessed
|
|
5
|
+
suffix separately, including dangling symlink targets.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { resolvePathPrefixSync, type ResolvedPathPrefix } from "@openclaw/fs-safe/advanced";
|
|
9
|
+
|
|
10
|
+
const observed: ResolvedPathPrefix = resolvePathPrefixSync("/srv/data/future/file.json");
|
|
11
|
+
// When /srv/data exists but future does not:
|
|
12
|
+
// observed.existingPath is the canonical spelling of /srv/data.
|
|
13
|
+
// observed.unresolvedSegments is ["future", "file.json"].
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The result has three readonly fields:
|
|
17
|
+
|
|
18
|
+
| Field | Meaning |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| `absolutePath` | Input anchored to the current directory or Windows drive, with raw path components retained and Windows separators converted to backslashes. |
|
|
21
|
+
| `existingPath` | Existing prefix canonicalized by fs-safe's native-realpath owner. This may be a file when the entire path exists. |
|
|
22
|
+
| `unresolvedSegments` | Components from the first missing entry onward, after expanding any earlier symlinks. Empty when the entire path exists. |
|
|
23
|
+
|
|
24
|
+
## Physical traversal and missing paths
|
|
25
|
+
|
|
26
|
+
The helper resolves `link/..` from the link's physical target. It does not use
|
|
27
|
+
`path.resolve()` on the full input or a symlink target, because lexical
|
|
28
|
+
normalization would erase that traversal. Relative inputs use the current
|
|
29
|
+
directory; Windows drive-relative inputs use Node's current directory for that
|
|
30
|
+
drive. A root-relative Windows symlink target retains its containing link's
|
|
31
|
+
drive or share root. Windows junctions and full UNC share roots are supported.
|
|
32
|
+
|
|
33
|
+
At the first `ENOENT`, traversal stops. A suffix such as
|
|
34
|
+
`missing/../live.sqlite` remains `["missing", "..", "live.sqlite"]`, even if
|
|
35
|
+
`live.sqlite` exists beside the missing component. Dot components, repeated
|
|
36
|
+
separators, and a trailing separator in the unresolved suffix are retained.
|
|
37
|
+
Normalizing that suffix would invent an alias to a file the filesystem cannot
|
|
38
|
+
reach through the missing directory. The caller owns any application-specific
|
|
39
|
+
comparison or prospective-path policy.
|
|
40
|
+
|
|
41
|
+
## Failures and limits
|
|
42
|
+
|
|
43
|
+
Only `ENOENT` from component inspection produces a missing suffix. Permission,
|
|
44
|
+
I/O, non-directory traversal, symlink-reading, and final canonicalization
|
|
45
|
+
failures propagate. A vanished symlink that was already observed is an error,
|
|
46
|
+
not an unprocessed missing component. NUL bytes reject with `invalid-path`.
|
|
47
|
+
|
|
48
|
+
Resolution rejects with `ELOOP` after 64 symlink expansions or a repeated
|
|
49
|
+
resolution state. The state retains exact bigint device/inode identity and
|
|
50
|
+
the remaining suffix, so revisiting a link with a shorter suffix is permitted.
|
|
51
|
+
Traversing through a non-directory, including `file/..`, `file/.`, or `file/`,
|
|
52
|
+
rejects with `ENOTDIR`.
|
|
53
|
+
|
|
54
|
+
Dot and parent components require the directory's search permission before
|
|
55
|
+
they are collapsed. Native realpath alone does not establish this permission
|
|
56
|
+
on every platform. Empty components from repeated or trailing separators do
|
|
57
|
+
not introduce a `.` lookup.
|
|
58
|
+
|
|
59
|
+
This is a read-only path observation. It neither pins files nor creates a root
|
|
60
|
+
boundary, authorizes access, or guarantees a consistent snapshot during
|
|
61
|
+
concurrent changes. Results can become stale immediately. Use a guarded Root
|
|
62
|
+
operation for subsequent access to untrusted paths; keep any database ownership,
|
|
63
|
+
cache invalidation, and mutation policy with the caller. Native configuration
|
|
64
|
+
and Bun realpath limitations follow the existing [runtime contract](install.md#bun-runtime).
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Path suffix alias probing
|
|
3
|
+
description: "Bounded local observations for selected missing relative path suffixes."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Path suffix alias probing
|
|
7
|
+
|
|
8
|
+
`probePathSuffixAliasesSync()` observes whether selected missing relative suffixes
|
|
9
|
+
would alias beneath an existing directory. It returns `boolean | undefined`; it
|
|
10
|
+
does not infer filesystem behavior from the operating system or normalize a path
|
|
11
|
+
into an authorization decision.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { probePathSuffixAliasesSync } from "@openclaw/fs-safe/advanced";
|
|
15
|
+
|
|
16
|
+
const aliases = probePathSuffixAliasesSync({
|
|
17
|
+
directory: "/trusted/existing-directory",
|
|
18
|
+
left: "Reports/Caf\u00e9",
|
|
19
|
+
right: "reports/Cafe\u0301",
|
|
20
|
+
});
|
|
21
|
+
if (aliases === undefined) {
|
|
22
|
+
// The caller must choose an explicit ambiguity policy.
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Use this for suffixes that do not yet exist. For local ASCII-case observations,
|
|
27
|
+
including an explicit read-only mode, use
|
|
28
|
+
[`probePathCaseInsensitiveSync()`](path-case.md). Suffix probing has no read-only
|
|
29
|
+
mode: a nontrivial observation can create and remove directories and requires an
|
|
30
|
+
approved writable parent.
|
|
31
|
+
|
|
32
|
+
## API and validation
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
type ProbePathSuffixAliasesOptions = {
|
|
36
|
+
directory: string;
|
|
37
|
+
left: string;
|
|
38
|
+
right: string;
|
|
39
|
+
shouldProbeCaseVariants?: (leftNfc: string, rightNfc: string) => boolean;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function probePathSuffixAliasesSync(
|
|
43
|
+
options: ProbePathSuffixAliasesOptions,
|
|
44
|
+
): boolean | undefined;
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The helper reads and validates `directory`, then resolves it to an absolute path
|
|
48
|
+
before reading the suffixes or predicate. Later option getters or the predicate
|
|
49
|
+
cannot retarget a relative directory by changing the working directory. When
|
|
50
|
+
filesystem observations are needed, the directory is canonicalized and its
|
|
51
|
+
identity is checked; an initial directory alias can be followed.
|
|
52
|
+
|
|
53
|
+
Suffixes must have the same number of ordinary relative path components. Empty
|
|
54
|
+
components, `.` and `..`, absolute suffixes, NUL characters, and non-string path
|
|
55
|
+
inputs are rejected with `TypeError`. Windows additionally rejects drive-relative
|
|
56
|
+
components, colons, and reserved device names, including device aliases with
|
|
57
|
+
extensions or trailing ignored characters. On POSIX, backslashes and colons are
|
|
58
|
+
ordinary filename characters. The optional predicate must be a function.
|
|
59
|
+
|
|
60
|
+
Both suffixes and the predicate are validated before the identical-suffix fast
|
|
61
|
+
path. Identical, valid, within-budget suffixes return `true` without filesystem
|
|
62
|
+
access or a predicate call. This does not prove that the directory exists or that
|
|
63
|
+
the suffix can be created.
|
|
64
|
+
|
|
65
|
+
## Fixed resource limits
|
|
66
|
+
|
|
67
|
+
These limits apply to one call and cannot be raised through options:
|
|
68
|
+
|
|
69
|
+
| Resource | Limit | On exceeding the limit |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| Each supplied suffix | 8,192 UTF-16 code units | `RangeError` before mutation |
|
|
72
|
+
| 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 |
|
|
74
|
+
| Directory-creation attempts | 128 | `undefined` after cleanup |
|
|
75
|
+
| Successfully created probe directories | 64 | `undefined` after cleanup |
|
|
76
|
+
| Forward filesystem observations | 4,096 | `undefined` after cleanup |
|
|
77
|
+
| Each generated actual path | 32,768 UTF-16 code units | `undefined` after cleanup |
|
|
78
|
+
|
|
79
|
+
Input limits are checked even for identical suffixes. Dynamic budgets count work
|
|
80
|
+
across the whole call, including collision retries; removing a probe does not
|
|
81
|
+
restore its creation budget. Cleanup is still attempted when a forward budget is
|
|
82
|
+
exhausted and is not disabled by that exhausted budget.
|
|
83
|
+
|
|
84
|
+
Candidates are generated lazily, only as needed. The helper does not eagerly
|
|
85
|
+
allocate every possible probe name or consume randomness for unused retries.
|
|
86
|
+
These are count and string-size limits, not a wall-clock guarantee. Synchronous
|
|
87
|
+
filesystem calls, randomness, and caller code can block; the helper cannot
|
|
88
|
+
interrupt them or promise a maximum elapsed time.
|
|
89
|
+
|
|
90
|
+
## Results and caller policy
|
|
91
|
+
|
|
92
|
+
- `true`: the suffixes are identical, or the requested observations found aliases.
|
|
93
|
+
- `false`: an observation found distinct entries, or the caller's predicate
|
|
94
|
+
excluded a pair.
|
|
95
|
+
- `undefined`: no reliable answer was obtained, including filesystem failures,
|
|
96
|
+
identity changes, exhausted collision candidates or dynamic budgets, generated
|
|
97
|
+
path limits, or incomplete cleanup.
|
|
98
|
+
|
|
99
|
+
`undefined` is not evidence of either case sensitivity or aliasing. The helper
|
|
100
|
+
does not cache observations, choose a fallback, reserve the future destination,
|
|
101
|
+
or establish a root-confinement boundary. A result applies only to the local
|
|
102
|
+
observations made during that call, not to every Unicode pair, mount, or later
|
|
103
|
+
filesystem state.
|
|
104
|
+
|
|
105
|
+
The optional trusted, synchronous `shouldProbeCaseVariants` predicate receives
|
|
106
|
+
NFC-normalized component pairs when they differ and are not equivalent under
|
|
107
|
+
ASCII case folding. Without a predicate, those pairs are eligible for probing.
|
|
108
|
+
It is called in component order. Returning `false` excludes
|
|
109
|
+
that pair and produces `false`; it is caller policy, not a filesystem finding.
|
|
110
|
+
A first-component exclusion needs no probes or randomness. A later exclusion
|
|
111
|
+
can follow earlier mutations, so it does not make the call read-only.
|
|
112
|
+
|
|
113
|
+
A non-boolean predicate result throws `TypeError`; a predicate exception is
|
|
114
|
+
re-thrown unchanged after cleanup attempts. Cleanup problems do not replace the
|
|
115
|
+
original predicate failure. Do not use an asynchronous predicate or rely on the
|
|
116
|
+
resource limits to bound arbitrary callback work.
|
|
117
|
+
|
|
118
|
+
## Probe design
|
|
119
|
+
|
|
120
|
+
The helper works through corresponding components, creating nested directories
|
|
121
|
+
to observe inherited lookup behavior. ASCII-case probes use generated names.
|
|
122
|
+
Normalization probes retain the original non-ASCII spellings while substituting
|
|
123
|
+
suitable ASCII letters. When a generated pair is unavailable, the exact raw pair
|
|
124
|
+
can be tried inside an owned neutral directory. Requested names are never
|
|
125
|
+
materialized directly in the caller's unowned parent.
|
|
126
|
+
|
|
127
|
+
Generated names are conservatively excluded when their NFC lowercase or uppercase
|
|
128
|
+
forms could collide with either requested component. These folds are only a name
|
|
129
|
+
exclusion rule; they never classify two requested paths as aliases. Short neutral
|
|
130
|
+
and ASCII probe names use at most six characters and are no longer than the
|
|
131
|
+
shorter requested component. Generated normalization pairs are also limited by
|
|
132
|
+
the longer original joined path length. A raw-pair fallback adds a directory
|
|
133
|
+
level; filesystem-specific name or path limits can still make it unavailable.
|
|
134
|
+
|
|
135
|
+
Probes use normal directory creation and the process umask, preserving inherited
|
|
136
|
+
directory behavior. They do not force mode `0700`, change parent permissions, or
|
|
137
|
+
promise private probe names. Creation and removal can affect directory timestamps
|
|
138
|
+
and filesystem watchers.
|
|
139
|
+
|
|
140
|
+
## Identity and cleanup limitations
|
|
141
|
+
|
|
142
|
+
The requested directory, canonical parent, and owned probe chain are rechecked
|
|
143
|
+
with exact bigint filesystem identities. An alternate spelling must identify the
|
|
144
|
+
same ordinary directory, not a symlink or unrelated collision. Existing files,
|
|
145
|
+
directories, and symlinks at a candidate name are not removed to make room.
|
|
146
|
+
|
|
147
|
+
Cleanup attempts owned directories in reverse order with identity checks and
|
|
148
|
+
non-recursive removal. Nonempty directories and observed replacements are
|
|
149
|
+
preserved. A cleanup failure changes an otherwise boolean result to `undefined`.
|
|
150
|
+
If the first identity observation after a successful creation fails, a directory
|
|
151
|
+
can remain: the helper does not guess ownership to delete it.
|
|
152
|
+
|
|
153
|
+
This is a pathname-based observation helper, not an atomic filesystem
|
|
154
|
+
transaction. There are unavoidable gaps between creation and the first identity
|
|
155
|
+
observation, and between an identity check and a subsequent syscall. No pinned
|
|
156
|
+
directory handle or atomic conditional deletion closes those gaps. Use a trusted,
|
|
157
|
+
approved writable directory and application-level concurrency control where
|
|
158
|
+
needed; do not use this helper as authorization to access attacker-controlled
|
|
159
|
+
paths.
|