@openclaw/fs-safe 0.11.0 → 0.13.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 +119 -0
- package/README.md +15 -8
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +32 -11
- package/dist/advanced.d.ts +3 -1
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +3 -1
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +4 -3
- package/dist/archive-gzip-tail.d.ts.map +1 -1
- package/dist/archive-gzip-tail.js +13 -9
- 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 +55 -46
- 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 +50 -17
- 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/bounded-read.js +2 -2
- 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/device-path.d.ts.map +1 -1
- package/dist/device-path.js +5 -3
- 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 +12 -2
- 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-prune.d.ts.map +1 -1
- package/dist/file-store-prune.js +9 -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 +1 -0
- package/dist/filename.d.ts.map +1 -1
- package/dist/filename.js +85 -23
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +3 -2
- package/dist/guarded-mkdir.d.ts +10 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +217 -33
- package/dist/guest.d.ts.map +1 -1
- package/dist/guest.js +16 -5
- package/dist/home-dir.d.ts.map +1 -1
- package/dist/home-dir.js +73 -10
- package/dist/install-path.d.ts +6 -0
- package/dist/install-path.d.ts.map +1 -1
- package/dist/install-path.js +67 -19
- package/dist/json-durable-queue-ownership.d.ts +8 -0
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +134 -43
- 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 +6 -0
- package/dist/json-durable-queue-read.d.ts.map +1 -0
- package/dist/json-durable-queue-read.js +61 -0
- package/dist/json-durable-queue.d.ts +1 -1
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +83 -134
- 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 +21 -0
- package/dist/native-binding.d.ts.map +1 -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-parent-admission.d.ts +31 -0
- package/dist/native-parent-admission.d.ts.map +1 -0
- package/dist/native-parent-admission.js +124 -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 +0 -3
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +309 -40
- 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 +14 -0
- package/dist/path-scope-lexical.d.ts.map +1 -0
- package/dist/path-scope-lexical.js +37 -0
- 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 +35 -6
- 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 +152 -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 +33 -29
- 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 +29 -0
- package/dist/root-boundary.d.ts.map +1 -0
- package/dist/root-boundary.js +182 -0
- package/dist/root-context.d.ts +12 -1
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +89 -16
- 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 +182 -33
- 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 +316 -325
- 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-move-preflight.d.ts +8 -0
- package/dist/root-move-preflight.d.ts.map +1 -0
- package/dist/root-move-preflight.js +16 -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 +2 -0
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +63 -22
- 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 +13 -0
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +263 -73
- 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 -29
- package/dist/root-paths.d.ts.map +1 -1
- package/dist/root-paths.js +89 -178
- 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-walk.d.ts.map +1 -1
- package/dist/root-walk.js +2 -1
- 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-mode.d.ts +2 -0
- package/dist/root-write-mode.d.ts.map +1 -1
- package/dist/root-write-mode.js +20 -7
- 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 +18 -7
- package/dist/safe-path-segment.d.ts.map +1 -1
- package/dist/safe-path-segment.js +3 -1
- 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-windows.d.ts +10 -0
- package/dist/secure-file-windows.d.ts.map +1 -0
- package/dist/secure-file-windows.js +186 -0
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +98 -10
- 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 -1
- package/dist/sibling-staged-file.d.ts.map +1 -1
- package/dist/sibling-staged-file.js +181 -50
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +27 -12
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +45 -15
- package/dist/sidecar-lock-root.d.ts.map +1 -1
- package/dist/sidecar-lock-root.js +4 -2
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +2 -3
- 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 +133 -4
- 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 +20 -16
- 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 +28 -1
- package/docs/archive.md +29 -2
- package/docs/atomic.md +27 -3
- package/docs/contributing.md +4 -1
- package/docs/copy.md +4 -1
- package/docs/durability.md +18 -2
- package/docs/errors.md +14 -6
- package/docs/file-store.md +30 -3
- package/docs/filename.md +21 -7
- package/docs/guest.md +5 -0
- package/docs/install-path.md +61 -15
- package/docs/install.md +12 -8
- 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 +28 -9
- package/docs/native.md +46 -10
- 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 +12 -1
- package/docs/permissions.md +26 -1
- package/docs/private-file-store.md +14 -0
- package/docs/public-api.md +14 -0
- package/docs/quickstart.md +1 -1
- package/docs/reading.md +8 -1
- package/docs/root.md +21 -4
- package/docs/secret-file.md +3 -0
- package/docs/secure-file.md +21 -16
- package/docs/security-model.md +76 -9
- package/docs/sidecar-lock.md +28 -1
- package/docs/store.md +27 -2
- package/docs/temp.md +262 -34
- package/docs/test-hooks.md +14 -0
- package/docs/writing.md +38 -8
- package/package.json +10 -10
package/dist/guarded-mkdir.js
CHANGED
|
@@ -1,18 +1,76 @@
|
|
|
1
|
-
import fsSync from "node:fs";
|
|
1
|
+
import fsSync, {} from "node:fs";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard } from "./directory-guard.js";
|
|
4
|
+
import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard, inspectDirectoryIdentitySync, } from "./directory-guard.js";
|
|
5
5
|
import { FsSafeError } from "./errors.js";
|
|
6
6
|
import { isNotFoundPathError, isPathRelativeEscape } from "./path.js";
|
|
7
|
-
import { directoryComponentNotDirectoryError } from "./root-errors.js";
|
|
7
|
+
import { directoryComponentNotDirectoryError, rootPathChangedError } from "./root-errors.js";
|
|
8
|
+
import { assertNoWindowsPathAlias, pathForWindowsFilesystem, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
8
9
|
import { realpathSync } from "./realpath.js";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
import { admitPathInsideRoot } from "./root-boundary.js";
|
|
11
|
+
import { checkedMutationDirectory } from "./pinned-mutation-observation.js";
|
|
12
|
+
import { createPathSegmentRoute, joinPathSegmentRoute, } from "./path-segment-route.js";
|
|
13
|
+
function sameDirectoryFacts(left, right) {
|
|
14
|
+
return left.dev === right.dev && left.ino === right.ino && left.mode === right.mode &&
|
|
15
|
+
left.nlink === right.nlink;
|
|
16
|
+
}
|
|
17
|
+
function inspectGuardCurrent(parent) {
|
|
18
|
+
const stat = inspectDirectoryIdentitySync(parent.dir, parent.stat);
|
|
19
|
+
if (realpathSync.native(parent.dir) !== parent.realPath) {
|
|
20
|
+
throw new FsSafeError("path-mismatch", "directory changed during operation");
|
|
21
|
+
}
|
|
22
|
+
return stat;
|
|
23
|
+
}
|
|
24
|
+
function observedGuard(parent) {
|
|
25
|
+
return checkedMutationDirectory(parent.dir, parent.realPath, parent.stat);
|
|
26
|
+
}
|
|
27
|
+
function createdDirectoryEvidence(admission, parent, childPath) {
|
|
28
|
+
try {
|
|
29
|
+
const parentAfter = inspectGuardCurrent(parent);
|
|
30
|
+
const childBefore = inspectDirectoryIdentitySync(childPath);
|
|
31
|
+
const realPath = realpathSync.native(childPath);
|
|
32
|
+
if (realPath !== childPath)
|
|
33
|
+
return undefined;
|
|
34
|
+
const childAfter = inspectDirectoryIdentitySync(childPath, childBefore);
|
|
35
|
+
if (!sameDirectoryFacts(childBefore, childAfter))
|
|
36
|
+
return undefined;
|
|
37
|
+
const child = checkedMutationDirectory(childPath, realPath, childAfter);
|
|
38
|
+
const receipt = Object.freeze({
|
|
39
|
+
admission,
|
|
40
|
+
parent: checkedMutationDirectory(parent.dir, parent.realPath, parentAfter),
|
|
41
|
+
child,
|
|
42
|
+
});
|
|
43
|
+
return Object.freeze({
|
|
44
|
+
receipt,
|
|
45
|
+
guard: Object.freeze({ dir: childPath, realPath, stat: childAfter }),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// Failed optional evidence leaves the existing ordered admission in charge.
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function sameAbsolutePath(left, right) {
|
|
54
|
+
return path.relative(path.resolve(left), path.resolve(right)) === "";
|
|
55
|
+
}
|
|
56
|
+
function suppliedExactRootIdentity(identity) {
|
|
57
|
+
return typeof identity?.dev === "bigint" && typeof identity.ino === "bigint"
|
|
58
|
+
? { dev: identity.dev, ino: identity.ino }
|
|
59
|
+
: undefined;
|
|
60
|
+
}
|
|
61
|
+
function assertGuardMatchesRootIdentity(guard, expected) {
|
|
62
|
+
if (guard.stat.dev !== expected.dev || guard.stat.ino !== expected.ino) {
|
|
63
|
+
throw rootPathChangedError();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function isPromiseLike(value) {
|
|
67
|
+
return typeof value?.then === "function";
|
|
12
68
|
}
|
|
13
69
|
async function realpathOrThrowNotFile(target) {
|
|
14
70
|
try {
|
|
15
|
-
|
|
71
|
+
const canonical = realpathSync.native(pathForWindowsFilesystem(target));
|
|
72
|
+
assertNoWindowsPathAlias(canonical, "filesystem", "canonical directory uses a Windows filesystem namespace alias");
|
|
73
|
+
return resolvePathPreservingWindowsRoot(canonical);
|
|
16
74
|
}
|
|
17
75
|
catch (error) {
|
|
18
76
|
if (isNotFoundPathError(error)) {
|
|
@@ -30,39 +88,154 @@ async function realpathOrThrowNotFile(target) {
|
|
|
30
88
|
* re-deriving it from the original, possibly-symlinked, lexical path.
|
|
31
89
|
*/
|
|
32
90
|
export async function mkdirPathComponentsWithGuards(params) {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
91
|
+
const rawRootReal = params.rootReal;
|
|
92
|
+
assertNoWindowsPathAlias(rawRootReal, "filesystem", "root directory uses a Windows filesystem namespace alias");
|
|
93
|
+
const rawTargetPath = params.targetPath;
|
|
94
|
+
assertNoWindowsPathAlias(rawTargetPath, "filesystem", "target directory uses a Windows filesystem namespace alias");
|
|
95
|
+
const root = resolvePathPreservingWindowsRoot(rawRootReal);
|
|
96
|
+
const target = resolvePathPreservingWindowsRoot(rawTargetPath);
|
|
97
|
+
const suppliedIdentity = suppliedExactRootIdentity(params.rootIdentity);
|
|
98
|
+
const configuredRootGuard = await createAsyncDirectoryGuard(root, { bigint: true });
|
|
99
|
+
const checkedRootIdentity = suppliedIdentity ?? {
|
|
100
|
+
dev: configuredRootGuard.stat.dev,
|
|
101
|
+
ino: configuredRootGuard.stat.ino,
|
|
102
|
+
};
|
|
103
|
+
assertGuardMatchesRootIdentity(configuredRootGuard, checkedRootIdentity);
|
|
104
|
+
assertNoWindowsPathAlias(configuredRootGuard.realPath);
|
|
105
|
+
const rootCanonical = resolvePathPreservingWindowsRoot(configuredRootGuard.realPath);
|
|
106
|
+
const rootGuard = rootCanonical === root
|
|
107
|
+
? configuredRootGuard
|
|
108
|
+
: await createAsyncDirectoryGuard(rootCanonical, { bigint: true });
|
|
109
|
+
assertGuardMatchesRootIdentity(rootGuard, checkedRootIdentity);
|
|
110
|
+
const admissionParams = {
|
|
111
|
+
candidatePath: target,
|
|
112
|
+
rootIdentity: checkedRootIdentity,
|
|
113
|
+
};
|
|
114
|
+
// Derive the suffix from the caller's trusted root spelling first. The
|
|
115
|
+
// canonical spelling is also accepted after both names have been bound to
|
|
116
|
+
// the same exact root object above.
|
|
117
|
+
const admittedTarget = admitPathInsideRoot({ rootPath: root, ...admissionParams }) ??
|
|
118
|
+
(rootCanonical === root
|
|
119
|
+
? undefined
|
|
120
|
+
: admitPathInsideRoot({ rootPath: rootCanonical, ...admissionParams }));
|
|
121
|
+
if (!admittedTarget || isPathRelativeEscape(admittedTarget.relativePath)) {
|
|
38
122
|
throw new FsSafeError("outside-workspace", "directory is outside workspace root");
|
|
39
123
|
}
|
|
40
|
-
let current =
|
|
41
|
-
|
|
124
|
+
let current = rootCanonical;
|
|
125
|
+
let currentGuard = rootGuard;
|
|
126
|
+
const parts = admittedTarget.relativePath.split(path.sep).filter(Boolean);
|
|
127
|
+
let partRoute;
|
|
128
|
+
let retainedTargetPath = params.retainedTargetPath &&
|
|
129
|
+
sameAbsolutePath(path.dirname(params.retainedTargetPath), target)
|
|
130
|
+
? params.retainedTargetPath
|
|
131
|
+
: undefined;
|
|
132
|
+
let retainedParentPath = retainedTargetPath ? path.dirname(retainedTargetPath) : undefined;
|
|
133
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
134
|
+
const part = parts[index];
|
|
42
135
|
const next = path.join(current, part);
|
|
43
|
-
const parentGuard =
|
|
44
|
-
|
|
136
|
+
const parentGuard = currentGuard;
|
|
137
|
+
assertNoWindowsPathAlias(parentGuard.realPath, "filesystem", "canonical parent directory uses a Windows filesystem namespace alias");
|
|
138
|
+
let created = false;
|
|
139
|
+
let createReceipt;
|
|
140
|
+
if (!params.revalidateParentAfterBeforeComponent) {
|
|
141
|
+
await assertAsyncDirectoryGuard(parentGuard);
|
|
142
|
+
}
|
|
45
143
|
await params.beforeComponent?.(next);
|
|
46
|
-
params.
|
|
47
|
-
|
|
48
|
-
await fs.mkdir(next, { mode: params.mode });
|
|
144
|
+
if (params.revalidateParentAfterBeforeComponent) {
|
|
145
|
+
await assertAsyncDirectoryGuard(parentGuard);
|
|
49
146
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
147
|
+
if (params.beforeCreateComponent) {
|
|
148
|
+
let missing = false;
|
|
149
|
+
try {
|
|
150
|
+
fsSync.lstatSync(next);
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
if (!isNotFoundPathError(error))
|
|
154
|
+
throw error;
|
|
155
|
+
missing = true;
|
|
156
|
+
}
|
|
157
|
+
if (missing) {
|
|
158
|
+
let prospectiveParent = retainedParentPath;
|
|
159
|
+
if (!prospectiveParent) {
|
|
160
|
+
partRoute ??= createPathSegmentRoute(parts);
|
|
161
|
+
prospectiveParent = joinPathSegmentRoute(next, partRoute, index + 1);
|
|
162
|
+
}
|
|
163
|
+
const authorization = params.beforeCreateComponent(next, prospectiveParent, retainedTargetPath, observedGuard(parentGuard));
|
|
164
|
+
if (isPromiseLike(authorization)) {
|
|
165
|
+
createReceipt = await authorization;
|
|
166
|
+
await assertAsyncDirectoryGuard(parentGuard);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
createReceipt = authorization;
|
|
170
|
+
// A synchronous authorization must still end at a live pathname
|
|
171
|
+
// fence immediately before the authority callback and mkdir.
|
|
172
|
+
if (!params.synchronousAuthorizationIncludesFence)
|
|
173
|
+
inspectGuardCurrent(parentGuard);
|
|
174
|
+
}
|
|
175
|
+
params.assertBeforeMutation?.();
|
|
176
|
+
// Authority callbacks are an explicit freshness boundary. Recheck the
|
|
177
|
+
// exact parent synchronously so no callback-observed authority is
|
|
178
|
+
// carried into a pathname mkdir after the parent was swapped.
|
|
179
|
+
inspectGuardCurrent(parentGuard);
|
|
180
|
+
try {
|
|
181
|
+
await fs.mkdir(next, { mode: params.mode });
|
|
182
|
+
created = true;
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
if (!error || typeof error !== "object" || !("code" in error) || error.code !== "EEXIST") {
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
params.assertBeforeMutation?.();
|
|
193
|
+
try {
|
|
194
|
+
await fs.mkdir(next, { mode: params.mode });
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
if (!error || typeof error !== "object" || !("code" in error) || error.code !== "EEXIST") {
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
let createdAuthorization;
|
|
203
|
+
let createdEvidence;
|
|
204
|
+
if (created && createReceipt && params.afterCreateComponent) {
|
|
205
|
+
createdEvidence = createdDirectoryEvidence(createReceipt, parentGuard, next);
|
|
206
|
+
if (createdEvidence) {
|
|
207
|
+
createdAuthorization = params.afterCreateComponent(createdEvidence.receipt);
|
|
53
208
|
}
|
|
54
209
|
}
|
|
55
|
-
|
|
56
|
-
|
|
210
|
+
if (params.beforeUseComponent && !createdAuthorization) {
|
|
211
|
+
let prospectiveParent = retainedParentPath;
|
|
212
|
+
if (!prospectiveParent) {
|
|
213
|
+
partRoute ??= createPathSegmentRoute(parts);
|
|
214
|
+
prospectiveParent = joinPathSegmentRoute(next, partRoute, index + 1);
|
|
215
|
+
}
|
|
216
|
+
await params.beforeUseComponent(next, prospectiveParent, retainedTargetPath);
|
|
217
|
+
await assertAsyncDirectoryGuard(parentGuard);
|
|
218
|
+
}
|
|
219
|
+
const stat = createdAuthorization ? undefined : fsSync.lstatSync(next);
|
|
220
|
+
if (stat && ((params.rejectSymlinks && stat.isSymbolicLink()) ||
|
|
221
|
+
(!stat.isSymbolicLink() && !stat.isDirectory()))) {
|
|
57
222
|
throw directoryComponentNotDirectoryError();
|
|
58
223
|
}
|
|
59
224
|
// Node's recursive mkdir follows symlinks in missing components. Build one
|
|
60
225
|
// segment at a time and realpath-check each segment before descending.
|
|
61
|
-
const nextReal =
|
|
62
|
-
|
|
226
|
+
const nextReal = createdAuthorization && createdEvidence
|
|
227
|
+
? createdEvidence.receipt.child.canonicalPath
|
|
228
|
+
: await realpathOrThrowNotFile(next);
|
|
229
|
+
const admittedNextReal = admitPathInsideRoot({
|
|
230
|
+
rootPath: rootCanonical,
|
|
231
|
+
candidatePath: nextReal,
|
|
232
|
+
rootIdentity: checkedRootIdentity,
|
|
233
|
+
});
|
|
234
|
+
if (!admittedNextReal) {
|
|
63
235
|
throw new FsSafeError("outside-workspace", "directory escaped workspace root");
|
|
64
236
|
}
|
|
65
|
-
|
|
237
|
+
const admittedNextPath = admittedNextReal.path;
|
|
238
|
+
if (stat?.isSymbolicLink()) {
|
|
66
239
|
// An existing path component may legitimately be a symlink to a real
|
|
67
240
|
// directory inside the root (e.g. a skill-bank layout). We already
|
|
68
241
|
// verified above that it resolves inside the root, so treat the
|
|
@@ -72,18 +245,29 @@ export async function mkdirPathComponentsWithGuards(params) {
|
|
|
72
245
|
// every subsequent segment. Callers that need the final directory
|
|
73
246
|
// (e.g. to guard it themselves after this function returns) must use
|
|
74
247
|
// the returned resolved path, not their own lexical parent path.
|
|
75
|
-
const targetStat = fsSync.statSync(
|
|
248
|
+
const targetStat = fsSync.statSync(admittedNextPath);
|
|
76
249
|
if (!targetStat.isDirectory()) {
|
|
77
250
|
throw directoryComponentNotDirectoryError();
|
|
78
251
|
}
|
|
79
|
-
await createAsyncDirectoryGuard(
|
|
252
|
+
currentGuard = await createAsyncDirectoryGuard(admittedNextPath, { bigint: true });
|
|
253
|
+
assertNoWindowsPathAlias(currentGuard.realPath, "filesystem", "canonical directory uses a Windows filesystem namespace alias");
|
|
80
254
|
await assertAsyncDirectoryGuard(parentGuard);
|
|
81
|
-
|
|
255
|
+
retainedTargetPath = undefined;
|
|
256
|
+
retainedParentPath = undefined;
|
|
257
|
+
current = admittedNextPath;
|
|
82
258
|
continue;
|
|
83
259
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
260
|
+
if (createdAuthorization && createdEvidence) {
|
|
261
|
+
currentGuard = createdEvidence.guard;
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
currentGuard = await createAsyncDirectoryGuard(admittedNextPath, { bigint: true });
|
|
265
|
+
}
|
|
266
|
+
assertNoWindowsPathAlias(currentGuard.realPath, "filesystem", "canonical directory uses a Windows filesystem namespace alias");
|
|
267
|
+
if (!createdAuthorization || !createdEvidence) {
|
|
268
|
+
await assertAsyncDirectoryGuard(parentGuard);
|
|
269
|
+
}
|
|
270
|
+
current = admittedNextPath;
|
|
87
271
|
}
|
|
88
272
|
return current;
|
|
89
273
|
}
|
package/dist/guest.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guest.d.ts","sourceRoot":"","sources":["../src/guest.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,yCAAyC,EAC1C,MAAM,0BAA0B,CAAC;AAElC,yFAAyF;AACzF,eAAO,MAAM,wCAAwC,KAAK,CAAC;AAC3D,8EAA8E;AAC9E,eAAO,MAAM,yCAAyC,IAAI,CAAC;AAE3D,gGAAgG;AAChG,eAAO,MAAM,uBAAuB,
|
|
1
|
+
{"version":3,"file":"guest.d.ts","sourceRoot":"","sources":["../src/guest.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,yCAAyC,EAC1C,MAAM,0BAA0B,CAAC;AAElC,yFAAyF;AACzF,eAAO,MAAM,wCAAwC,KAAK,CAAC;AAC3D,8EAA8E;AAC9E,eAAO,MAAM,yCAAyC,IAAI,CAAC;AAE3D,gGAAgG;AAChG,eAAO,MAAM,uBAAuB,QA8ZxB,CAAC;AAEb,OAAO,EAAE,yCAAyC,EAAE,CAAC"}
|
package/dist/guest.js
CHANGED
|
@@ -57,7 +57,10 @@ export const GUEST_FILESYSTEM_PYTHON = [
|
|
|
57
57
|
" except FileNotFoundError:",
|
|
58
58
|
" if not mkdir_enabled:",
|
|
59
59
|
" raise",
|
|
60
|
-
"
|
|
60
|
+
" try:",
|
|
61
|
+
" os.mkdir(segment, 0o777, dir_fd=current_fd)",
|
|
62
|
+
" except FileExistsError:",
|
|
63
|
+
" pass # The competing entry must still pass the no-follow directory open.",
|
|
61
64
|
" next_fd = open_dir(segment, dir_fd=current_fd)",
|
|
62
65
|
" os.close(current_fd)",
|
|
63
66
|
" current_fd = next_fd",
|
|
@@ -356,11 +359,19 @@ export const GUEST_FILESYSTEM_PYTHON = [
|
|
|
356
359
|
" return",
|
|
357
360
|
" if stat.S_ISLNK(src_stat.st_mode):",
|
|
358
361
|
" link_target = os.readlink(src_basename, dir_fd=src_parent_fd)",
|
|
362
|
+
" temp_dir_name, temp_dir_fd = create_staging_dir(dst_parent_fd)",
|
|
359
363
|
" try:",
|
|
360
|
-
" os.
|
|
361
|
-
"
|
|
362
|
-
"
|
|
363
|
-
"
|
|
364
|
+
" os.symlink(link_target, 'link', dir_fd=temp_dir_fd)",
|
|
365
|
+
" os.replace('link', dst_basename, src_dir_fd=temp_dir_fd, dst_dir_fd=dst_parent_fd)",
|
|
366
|
+
" finally:",
|
|
367
|
+
" try:",
|
|
368
|
+
" try:",
|
|
369
|
+
" os.unlink('link', dir_fd=temp_dir_fd)",
|
|
370
|
+
" except FileNotFoundError:",
|
|
371
|
+
" pass",
|
|
372
|
+
" finally:",
|
|
373
|
+
" os.close(temp_dir_fd)",
|
|
374
|
+
" os.rmdir(temp_dir_name, dir_fd=dst_parent_fd)",
|
|
364
375
|
" os.unlink(src_basename, dir_fd=src_parent_fd)",
|
|
365
376
|
" os.fsync(dst_parent_fd)",
|
|
366
377
|
" os.fsync(src_parent_fd)",
|
package/dist/home-dir.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"home-dir.d.ts","sourceRoot":"","sources":["../src/home-dir.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"home-dir.d.ts","sourceRoot":"","sources":["../src/home-dir.ts"],"names":[],"mappings":"AAsCA,wBAAgB,uBAAuB,CACrC,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,OAAO,GAAE,MAAM,MAAmB,GACjC,MAAM,GAAG,SAAS,CAOpB;AAwCD,wBAAgB,sBAAsB,CACpC,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,OAAO,GAAE,MAAM,MAAmB,GACjC,MAAM,CAIR;AAED,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IACL,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,MAAM,CAAC;CACxB,GACA,MAAM,CAYR;AA4DD,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IACL,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,MAAM,CAAC;CACxB,GACA,MAAM,CAoBR"}
|
package/dist/home-dir.js
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
3
4
|
import { normalizeOptionalString } from "./string-coerce.js";
|
|
5
|
+
import { assertNoWindowsPathAlias, assertNoWindowsPathAliasForPlatform, repairResolvedWindowsRoot, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
6
|
+
const PATH_ALIAS_MESSAGE = "path uses a Windows filesystem namespace alias";
|
|
7
|
+
function isOrdinaryRootedWindowsDrivePath(input) {
|
|
8
|
+
const drive = input.charCodeAt(0);
|
|
9
|
+
return input.length >= 3 &&
|
|
10
|
+
((drive >= 0x41 && drive <= 0x5a) || (drive >= 0x61 && drive <= 0x7a)) &&
|
|
11
|
+
input.charCodeAt(1) === 0x3a &&
|
|
12
|
+
(input.charCodeAt(2) === 0x5c || input.charCodeAt(2) === 0x2f) &&
|
|
13
|
+
input.indexOf(":", 2) === -1;
|
|
14
|
+
}
|
|
4
15
|
function hasHomePrefix(input) {
|
|
5
16
|
return input === "~" || input.startsWith("~/") ||
|
|
6
17
|
(path.sep === "\\" && input.startsWith("~\\"));
|
|
@@ -17,7 +28,12 @@ function normalize(value) {
|
|
|
17
28
|
}
|
|
18
29
|
export function resolveEffectiveHomeDir(env = process.env, homedir = os.homedir) {
|
|
19
30
|
const raw = resolveRawHomeDir(env, homedir);
|
|
20
|
-
|
|
31
|
+
if (!raw)
|
|
32
|
+
return undefined;
|
|
33
|
+
assertNoWindowsPathAlias(raw, "filesystem", "home path uses a Windows filesystem namespace alias");
|
|
34
|
+
const resolved = resolvePathPreservingWindowsRoot(raw);
|
|
35
|
+
assertNoWindowsPathAlias(resolved, "filesystem", "home path uses a Windows filesystem namespace alias");
|
|
36
|
+
return resolved;
|
|
21
37
|
}
|
|
22
38
|
function resolveRawHomeDir(env, homedir) {
|
|
23
39
|
const explicitHome = normalize(env.OPENCLAW_HOME);
|
|
@@ -56,7 +72,9 @@ function normalizeSafe(homedir) {
|
|
|
56
72
|
}
|
|
57
73
|
}
|
|
58
74
|
export function resolveRequiredHomeDir(env = process.env, homedir = os.homedir) {
|
|
59
|
-
|
|
75
|
+
const resolved = resolveEffectiveHomeDir(env, homedir) ?? path.resolve(process.cwd());
|
|
76
|
+
assertNoWindowsPathAlias(resolved, "filesystem", "home path uses a Windows filesystem namespace alias");
|
|
77
|
+
return resolved;
|
|
60
78
|
}
|
|
61
79
|
export function expandHomePrefix(input, opts) {
|
|
62
80
|
if (!hasHomePrefix(input)) {
|
|
@@ -70,17 +88,62 @@ export function expandHomePrefix(input, opts) {
|
|
|
70
88
|
// Expand before normalizing so a following .. traverses the actual home.
|
|
71
89
|
return path.join(home, input.slice(2));
|
|
72
90
|
}
|
|
73
|
-
|
|
74
|
-
if (!input) {
|
|
75
|
-
return input;
|
|
76
|
-
}
|
|
77
|
-
if (!hasHomePrefix(input)) {
|
|
78
|
-
return path.resolve(input);
|
|
79
|
-
}
|
|
91
|
+
function resolveExpandedHomePath(input, opts) {
|
|
80
92
|
const expanded = expandHomePrefix(input, {
|
|
81
93
|
home: resolveRequiredHomeDir(opts?.env ?? process.env, opts?.homedir ?? os.homedir),
|
|
82
94
|
env: opts?.env,
|
|
83
95
|
homedir: opts?.homedir,
|
|
84
96
|
});
|
|
85
|
-
|
|
97
|
+
const resolved = resolvePathPreservingWindowsRoot(expanded);
|
|
98
|
+
assertNoWindowsPathAlias(resolved, "filesystem", PATH_ALIAS_MESSAGE);
|
|
99
|
+
return resolved;
|
|
100
|
+
}
|
|
101
|
+
function admitFinalHomePath(resolved, platform) {
|
|
102
|
+
assertNoWindowsPathAliasForPlatform(resolved, "filesystem", PATH_ALIAS_MESSAGE, platform);
|
|
103
|
+
return resolved;
|
|
104
|
+
}
|
|
105
|
+
function finishOrdinaryHomePath(input, resolved) {
|
|
106
|
+
const resolvedPlatform = process.platform;
|
|
107
|
+
if (resolved === input && resolvedPlatform !== undefined)
|
|
108
|
+
return resolved;
|
|
109
|
+
return admitFinalHomePath(resolved, resolvedPlatform);
|
|
110
|
+
}
|
|
111
|
+
function repairAndFinishOrdinaryHomePath(input, resolved) {
|
|
112
|
+
return finishOrdinaryHomePath(input, repairResolvedWindowsRoot(input, resolved));
|
|
113
|
+
}
|
|
114
|
+
function resolveHomePathCold(input, opts, rawPlatform, ordinaryRawAdmitted) {
|
|
115
|
+
if (ordinaryRawAdmitted) {
|
|
116
|
+
return finishOrdinaryHomePath(input, resolvePathPreservingWindowsRoot(input));
|
|
117
|
+
}
|
|
118
|
+
assertNoWindowsPathAliasForPlatform(input, "filesystem", PATH_ALIAS_MESSAGE, rawPlatform);
|
|
119
|
+
if (!hasHomePrefix(input)) {
|
|
120
|
+
const resolved = resolvePathPreservingWindowsRoot(input);
|
|
121
|
+
// This cold branch was not admitted as an ordinary rooted drive. Recheck
|
|
122
|
+
// even an unchanged result because the live platform can change between
|
|
123
|
+
// raw admission and resolved-path admission. Passing undefined preserves
|
|
124
|
+
// the classifier's default process.platform read.
|
|
125
|
+
return admitFinalHomePath(resolved, process.platform);
|
|
126
|
+
}
|
|
127
|
+
return resolveExpandedHomePath(input, opts);
|
|
128
|
+
}
|
|
129
|
+
export function resolveHomeRelativePath(input, opts) {
|
|
130
|
+
if (!input) {
|
|
131
|
+
return input;
|
|
132
|
+
}
|
|
133
|
+
const rawPlatform = process.platform;
|
|
134
|
+
// Primitive ordinary drive paths have exactly one structural colon. Record
|
|
135
|
+
// that admission so an unchanged resolved string does not need rescanning.
|
|
136
|
+
const ordinaryRawAdmitted = rawPlatform === "win32" &&
|
|
137
|
+
typeof input === "string" &&
|
|
138
|
+
isOrdinaryRootedWindowsDrivePath(input);
|
|
139
|
+
// Keep the overwhelmingly common rooted-drive path close to path.resolve
|
|
140
|
+
// itself. Seven-byte inputs retain the general helper's observable
|
|
141
|
+
// namespace-root preflight; a rare six-byte result retains its repair.
|
|
142
|
+
if (!ordinaryRawAdmitted || input.length === 7) {
|
|
143
|
+
return resolveHomePathCold(input, opts, rawPlatform, ordinaryRawAdmitted);
|
|
144
|
+
}
|
|
145
|
+
const resolved = path.resolve(input);
|
|
146
|
+
return resolved.length === 6
|
|
147
|
+
? repairAndFinishOrdinaryHomePath(input, resolved)
|
|
148
|
+
: finishOrdinaryHomePath(input, resolved);
|
|
86
149
|
}
|
package/dist/install-path.d.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
export declare function safeDirName(input: string): string;
|
|
2
|
+
/** Legacy readable encoding; distinct IDs can share a result. Use V2 for untrusted IDs. */
|
|
2
3
|
export declare function safePathSegmentHashed(input: string): string;
|
|
4
|
+
/**
|
|
5
|
+
* Versioned install-ID encoding: every trimmed ID receives a domain-separated
|
|
6
|
+
* SHA-256 digest. Only surrounding whitespace is intentionally equivalent.
|
|
7
|
+
*/
|
|
8
|
+
export declare function safePathSegmentHashedV2(input: string): string;
|
|
3
9
|
export declare function resolveSafeInstallDir(params: {
|
|
4
10
|
baseDir: string;
|
|
5
11
|
id: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"install-path.d.ts","sourceRoot":"","sources":["../src/install-path.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"install-path.d.ts","sourceRoot":"","sources":["../src/install-path.ts"],"names":[],"mappings":"AAWA,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMjD;AAED,2FAA2F;AAC3F,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAiB3D;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAO7D;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,kBAAkB,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;CACtC,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CA+B5D;AAED,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuEhB"}
|
package/dist/install-path.js
CHANGED
|
@@ -3,6 +3,7 @@ import fsSync from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { isPathInside } from "./path.js";
|
|
5
5
|
import { realpathSync } from "./realpath.js";
|
|
6
|
+
import { hasWindowsPathAlias, pathForWindowsFilesystem, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
6
7
|
export function safeDirName(input) {
|
|
7
8
|
const trimmed = input.trim();
|
|
8
9
|
if (!trimmed) {
|
|
@@ -10,6 +11,7 @@ export function safeDirName(input) {
|
|
|
10
11
|
}
|
|
11
12
|
return trimmed.replaceAll("/", "__").replaceAll("\\", "__");
|
|
12
13
|
}
|
|
14
|
+
/** Legacy readable encoding; distinct IDs can share a result. Use V2 for untrusted IDs. */
|
|
13
15
|
export function safePathSegmentHashed(input) {
|
|
14
16
|
const trimmed = input.trim();
|
|
15
17
|
const base = trimmed
|
|
@@ -26,11 +28,37 @@ export function safePathSegmentHashed(input) {
|
|
|
26
28
|
}
|
|
27
29
|
return safe;
|
|
28
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Versioned install-ID encoding: every trimmed ID receives a domain-separated
|
|
33
|
+
* SHA-256 digest. Only surrounding whitespace is intentionally equivalent.
|
|
34
|
+
*/
|
|
35
|
+
export function safePathSegmentHashedV2(input) {
|
|
36
|
+
const hash = createHash("sha256")
|
|
37
|
+
.update("@openclaw/fs-safe:install-path:v2\0", "utf8")
|
|
38
|
+
// UTF-16LE preserves every JavaScript code unit, including lone surrogates.
|
|
39
|
+
.update(input.trim(), "utf16le")
|
|
40
|
+
.digest("hex");
|
|
41
|
+
return `id-v2-${hash}`;
|
|
42
|
+
}
|
|
29
43
|
export function resolveSafeInstallDir(params) {
|
|
44
|
+
const baseDir = params.baseDir;
|
|
45
|
+
if (hasWindowsPathAlias(baseDir, "filesystem")) {
|
|
46
|
+
return { ok: false, error: params.invalidNameMessage };
|
|
47
|
+
}
|
|
30
48
|
const encodedName = (params.nameEncoder ?? safeDirName)(params.id);
|
|
31
|
-
|
|
32
|
-
|
|
49
|
+
if (hasWindowsPathAlias(encodedName, "relative")) {
|
|
50
|
+
return { ok: false, error: params.invalidNameMessage };
|
|
51
|
+
}
|
|
52
|
+
const targetDir = path.join(baseDir, encodedName);
|
|
53
|
+
if (hasWindowsPathAlias(targetDir, "filesystem")) {
|
|
54
|
+
return { ok: false, error: params.invalidNameMessage };
|
|
55
|
+
}
|
|
56
|
+
const resolvedBase = resolvePathPreservingWindowsRoot(baseDir);
|
|
33
57
|
const resolvedTarget = path.resolve(targetDir);
|
|
58
|
+
if (hasWindowsPathAlias(resolvedBase, "filesystem") ||
|
|
59
|
+
hasWindowsPathAlias(resolvedTarget, "filesystem")) {
|
|
60
|
+
return { ok: false, error: params.invalidNameMessage };
|
|
61
|
+
}
|
|
34
62
|
const relative = path.relative(resolvedBase, resolvedTarget);
|
|
35
63
|
if (!relative ||
|
|
36
64
|
relative === ".." ||
|
|
@@ -41,40 +69,58 @@ export function resolveSafeInstallDir(params) {
|
|
|
41
69
|
return { ok: true, path: resolvedTarget };
|
|
42
70
|
}
|
|
43
71
|
export async function assertCanonicalPathWithinBase(params) {
|
|
44
|
-
const
|
|
45
|
-
const
|
|
72
|
+
const baseDirInput = params.baseDir;
|
|
73
|
+
const candidatePathInput = params.candidatePath;
|
|
74
|
+
const boundaryLabel = params.boundaryLabel;
|
|
75
|
+
const invalidPath = () => new Error(`Invalid path: must stay within ${boundaryLabel}`);
|
|
76
|
+
const assertAdmittedPath = (value) => {
|
|
77
|
+
if (hasWindowsPathAlias(value, "filesystem"))
|
|
78
|
+
throw invalidPath();
|
|
79
|
+
};
|
|
80
|
+
assertAdmittedPath(baseDirInput);
|
|
81
|
+
assertAdmittedPath(candidatePathInput);
|
|
82
|
+
const baseDir = resolvePathPreservingWindowsRoot(baseDirInput);
|
|
83
|
+
const candidatePath = resolvePathPreservingWindowsRoot(candidatePathInput);
|
|
84
|
+
assertAdmittedPath(baseDir);
|
|
85
|
+
assertAdmittedPath(candidatePath);
|
|
46
86
|
if (!isPathInside(baseDir, candidatePath)) {
|
|
47
|
-
throw
|
|
87
|
+
throw invalidPath();
|
|
48
88
|
}
|
|
49
|
-
const
|
|
89
|
+
const baseOperationPath = pathForWindowsFilesystem(baseDir);
|
|
90
|
+
const baseLstat = fsSync.lstatSync(baseOperationPath);
|
|
50
91
|
if (baseLstat.isSymbolicLink()) {
|
|
51
|
-
const baseStat = fsSync.statSync(
|
|
92
|
+
const baseStat = fsSync.statSync(baseOperationPath);
|
|
52
93
|
if (!baseStat.isDirectory()) {
|
|
53
|
-
throw new Error(`Invalid ${
|
|
94
|
+
throw new Error(`Invalid ${boundaryLabel}: base directory must resolve to a directory`);
|
|
54
95
|
}
|
|
55
96
|
}
|
|
56
97
|
else if (!baseLstat.isDirectory()) {
|
|
57
|
-
throw new Error(`Invalid ${
|
|
98
|
+
throw new Error(`Invalid ${boundaryLabel}: base directory must be a directory`);
|
|
58
99
|
}
|
|
59
|
-
const baseRealPath = realpathSync.native(
|
|
100
|
+
const baseRealPath = realpathSync.native(baseOperationPath);
|
|
101
|
+
assertAdmittedPath(baseRealPath);
|
|
60
102
|
const validateDirectory = async (dirPath) => {
|
|
61
|
-
|
|
62
|
-
const
|
|
103
|
+
assertAdmittedPath(dirPath);
|
|
104
|
+
const resolvedDirPath = resolvePathPreservingWindowsRoot(dirPath);
|
|
105
|
+
assertAdmittedPath(resolvedDirPath);
|
|
106
|
+
const operationPath = pathForWindowsFilesystem(dirPath);
|
|
107
|
+
const dirLstat = fsSync.lstatSync(operationPath);
|
|
63
108
|
if (dirLstat.isSymbolicLink()) {
|
|
64
109
|
if (resolvedDirPath !== baseDir) {
|
|
65
|
-
throw new Error(`Invalid path: must stay within ${
|
|
110
|
+
throw new Error(`Invalid path: must stay within ${boundaryLabel}`);
|
|
66
111
|
}
|
|
67
|
-
const dirStat = fsSync.statSync(
|
|
112
|
+
const dirStat = fsSync.statSync(operationPath);
|
|
68
113
|
if (!dirStat.isDirectory()) {
|
|
69
|
-
throw new Error(`Invalid path: must stay within ${
|
|
114
|
+
throw new Error(`Invalid path: must stay within ${boundaryLabel}`);
|
|
70
115
|
}
|
|
71
116
|
}
|
|
72
117
|
else if (!dirLstat.isDirectory()) {
|
|
73
|
-
throw new Error(`Invalid path: must stay within ${
|
|
118
|
+
throw new Error(`Invalid path: must stay within ${boundaryLabel}`);
|
|
74
119
|
}
|
|
75
|
-
const dirRealPath = realpathSync.native(
|
|
120
|
+
const dirRealPath = realpathSync.native(operationPath);
|
|
121
|
+
assertAdmittedPath(dirRealPath);
|
|
76
122
|
if (!isPathInside(baseRealPath, dirRealPath)) {
|
|
77
|
-
throw
|
|
123
|
+
throw invalidPath();
|
|
78
124
|
}
|
|
79
125
|
};
|
|
80
126
|
try {
|
|
@@ -87,5 +133,7 @@ export async function assertCanonicalPathWithinBase(params) {
|
|
|
87
133
|
throw err;
|
|
88
134
|
}
|
|
89
135
|
}
|
|
90
|
-
|
|
136
|
+
const candidateParent = path.dirname(candidatePath);
|
|
137
|
+
assertAdmittedPath(candidateParent);
|
|
138
|
+
await validateDirectory(candidateParent);
|
|
91
139
|
}
|
|
@@ -1,12 +1,20 @@
|
|
|
1
|
+
import { type BigIntStats } from "node:fs";
|
|
1
2
|
export type DurableQueueEntryPathsLike = {
|
|
2
3
|
jsonPath: string;
|
|
3
4
|
deliveredPath: string;
|
|
4
5
|
processingPath?: string;
|
|
5
6
|
};
|
|
7
|
+
export type ValidatedDurableQueueEntryPaths = Readonly<{
|
|
8
|
+
jsonPath: string;
|
|
9
|
+
deliveredPath: string;
|
|
10
|
+
processingPath: string;
|
|
11
|
+
}>;
|
|
12
|
+
export declare function validateDurableQueueEntryPaths(paths: DurableQueueEntryPathsLike): ValidatedDurableQueueEntryPaths;
|
|
6
13
|
export declare function durableQueueProcessingPath(paths: DurableQueueEntryPathsLike): string;
|
|
7
14
|
export declare function claimDurableQueueEntry(paths: DurableQueueEntryPathsLike, options?: {
|
|
8
15
|
skipUnowned?: boolean;
|
|
9
16
|
}): Promise<string | null>;
|
|
17
|
+
export declare function migrateDurableQueueEntry<T>(paths: DurableQueueEntryPathsLike, expected: BigIntStats, releaseReadPin: () => Promise<void>, run: (filePath: string, beforePublish: () => Promise<void>) => Promise<T>): Promise<T>;
|
|
10
18
|
export declare function completeDeliveredQueueEntry(paths: DurableQueueEntryPathsLike): Promise<boolean>;
|
|
11
19
|
export declare function acknowledgeDurableQueueEntry(paths: DurableQueueEntryPathsLike): Promise<void>;
|
|
12
20
|
export declare function moveDurableQueueEntryToFailed(params: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json-durable-queue-ownership.d.ts","sourceRoot":"","sources":["../src/json-durable-queue-ownership.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"json-durable-queue-ownership.d.ts","sourceRoot":"","sources":["../src/json-durable-queue-ownership.ts"],"names":[],"mappings":"AAEA,OAAe,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAUnD,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,+BAA+B,GAAG,QAAQ,CAAC;IACrD,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC,CAAC;AAUH,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,0BAA0B,GAChC,+BAA+B,CAkBjC;AAiBD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,0BAA0B,GAAG,MAAM,CAIpF;AA0ED,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,0BAA0B,EACjC,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAO,GACtC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAOxB;AAGD,wBAAsB,wBAAwB,CAAC,CAAC,EAC9C,KAAK,EAAE,0BAA0B,EACjC,QAAQ,EAAE,WAAW,EACrB,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EACnC,GAAG,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GACxE,OAAO,CAAC,CAAC,CAAC,CAmCZ;AAED,wBAAsB,2BAA2B,CAC/C,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,OAAO,CAAC,CAclB;AAED,wBAAsB,4BAA4B,CAChD,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,IAAI,CAAC,CA0Bf;AAED,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,KAAK,EAAE,0BAA0B,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDhB;AAKD,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAI1D"}
|