@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
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import fsSync, {} from "node:fs";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { inspectDirectoryIdentitySync, observeDirectoryIdentitySync, } from "./directory-guard.js";
|
|
5
|
+
import { admitTempWorkspaceChild, admitTempWorkspaceChildSync, } from "./temp-workspace-child-admission.js";
|
|
6
|
+
import { FsSafeError } from "./errors.js";
|
|
7
|
+
import { recordFileObservationFailure } from "./file-observation.js";
|
|
8
|
+
import { realpathSync } from "./realpath.js";
|
|
9
|
+
import { assertNoWindowsPathAlias, pathForWindowsFilesystem, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
10
|
+
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
11
|
+
import { inspectTempWorkspaceDescriptorIdentitySync, projectTempWorkspaceNumericIdentity, TEMP_WORKSPACE_NUMERIC_IDENTITY_REPLAY, } from "./temp-workspace-identity.js";
|
|
12
|
+
import { assertTrustedTempWorkspaceDirectory } from "./temp-workspace-permissions.js";
|
|
13
|
+
const WINDOWS = process.platform === "win32";
|
|
14
|
+
function effectiveOwner() {
|
|
15
|
+
// Windows mode/uid fields do not describe ACL authority. The supplied root's
|
|
16
|
+
// ACL remains a caller trust requirement, independently of cleanup safety.
|
|
17
|
+
if (WINDOWS)
|
|
18
|
+
return undefined;
|
|
19
|
+
let uid;
|
|
20
|
+
try {
|
|
21
|
+
uid = process.geteuid?.();
|
|
22
|
+
}
|
|
23
|
+
catch (cause) {
|
|
24
|
+
throw new FsSafeError("permission-unverified", "temp workspace requires effective owner identity", { cause });
|
|
25
|
+
}
|
|
26
|
+
if (!Number.isSafeInteger(uid) || uid === undefined || uid < 0) {
|
|
27
|
+
throw new FsSafeError("permission-unverified", "temp workspace requires effective owner identity");
|
|
28
|
+
}
|
|
29
|
+
return uid;
|
|
30
|
+
}
|
|
31
|
+
function safeNumericIdentity(stat) {
|
|
32
|
+
const numeric = projectTempWorkspaceNumericIdentity(stat);
|
|
33
|
+
return numeric && (!WINDOWS || (numeric.dev !== 0 && numeric.ino !== 0))
|
|
34
|
+
? numeric
|
|
35
|
+
: undefined;
|
|
36
|
+
}
|
|
37
|
+
function copyExactDirectoryObservation(stat) {
|
|
38
|
+
return Object.freeze({
|
|
39
|
+
dev: stat.dev,
|
|
40
|
+
ino: stat.ino,
|
|
41
|
+
uid: stat.uid,
|
|
42
|
+
mode: stat.mode,
|
|
43
|
+
directory: stat.isDirectory(),
|
|
44
|
+
symbolicLink: stat.isSymbolicLink(),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function snapshotFromExactObservation(dir, uid, realPath, stat) {
|
|
48
|
+
if (stat.symbolicLink || !stat.directory) {
|
|
49
|
+
throw new FsSafeError("not-file", "temp workspace root component must be a real directory");
|
|
50
|
+
}
|
|
51
|
+
assertTrustedTempWorkspaceDirectory(stat, uid);
|
|
52
|
+
const identity = Object.freeze({ dev: stat.dev, ino: stat.ino });
|
|
53
|
+
// Retain copied immutable scalars, never a mutable Stats object supplied by
|
|
54
|
+
// an observation hook. Every later permission check uses a fresh snapshot.
|
|
55
|
+
return Object.freeze({ dir, identity, numericIdentity: safeNumericIdentity(stat), realPath });
|
|
56
|
+
}
|
|
57
|
+
function canonicalTempWorkspacePath(dir) {
|
|
58
|
+
assertNoWindowsPathAlias(dir, "filesystem");
|
|
59
|
+
const canonical = realpathSync.native(pathForWindowsFilesystem(dir));
|
|
60
|
+
assertNoWindowsPathAlias(canonical, "filesystem");
|
|
61
|
+
return canonical;
|
|
62
|
+
}
|
|
63
|
+
function snapshot(dir, uid, realPath = canonicalTempWorkspacePath(dir)) {
|
|
64
|
+
const stat = inspectDirectoryIdentitySync(dir);
|
|
65
|
+
const observation = copyExactDirectoryObservation(stat);
|
|
66
|
+
return { entry: snapshotFromExactObservation(dir, uid, realPath, observation), stat };
|
|
67
|
+
}
|
|
68
|
+
function hasCompleteExactIdentity(stat) {
|
|
69
|
+
return typeof stat.dev === "bigint" && typeof stat.ino === "bigint" &&
|
|
70
|
+
(!WINDOWS || (stat.dev !== 0n && stat.ino !== 0n));
|
|
71
|
+
}
|
|
72
|
+
function assertCanonicalRoot(entry) {
|
|
73
|
+
if (canonicalTempWorkspacePath(entry.dir) !== entry.realPath) {
|
|
74
|
+
throw new FsSafeError("path-mismatch", "temp workspace root ancestry changed");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function identityMismatch() {
|
|
78
|
+
const error = new FsSafeError("path-mismatch", "file identity changed or could not be verified");
|
|
79
|
+
recordFileObservationFailure(error, "identity");
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
function inspectSnapshotIdentity(entry) {
|
|
83
|
+
const expected = entry.numericIdentity;
|
|
84
|
+
if (!expected) {
|
|
85
|
+
return inspectDirectoryIdentitySync(entry.dir, entry.identity);
|
|
86
|
+
}
|
|
87
|
+
const stat = observeDirectoryIdentitySync(entry.dir);
|
|
88
|
+
let requiresExactRetry = false;
|
|
89
|
+
const devKnown = Number.isSafeInteger(stat.dev) && stat.dev >= 0 && (!WINDOWS || stat.dev !== 0);
|
|
90
|
+
if (devKnown) {
|
|
91
|
+
if (stat.dev !== expected.dev)
|
|
92
|
+
identityMismatch();
|
|
93
|
+
}
|
|
94
|
+
else if (WINDOWS)
|
|
95
|
+
requiresExactRetry = true;
|
|
96
|
+
else
|
|
97
|
+
identityMismatch();
|
|
98
|
+
const inoKnown = Number.isSafeInteger(stat.ino) && stat.ino >= 0 && (!WINDOWS || stat.ino !== 0);
|
|
99
|
+
if (inoKnown) {
|
|
100
|
+
if (stat.ino !== expected.ino)
|
|
101
|
+
identityMismatch();
|
|
102
|
+
}
|
|
103
|
+
else if (WINDOWS)
|
|
104
|
+
requiresExactRetry = true;
|
|
105
|
+
else
|
|
106
|
+
identityMismatch();
|
|
107
|
+
if (!requiresExactRetry)
|
|
108
|
+
return stat;
|
|
109
|
+
// Read exact identity only once. The strict helper may re-check this constant
|
|
110
|
+
// receipt in memory when Windows still reports an unknown component.
|
|
111
|
+
const exact = observeDirectoryIdentitySync(entry.dir, { bigint: true });
|
|
112
|
+
return inspectFileIdentitySync(() => exact, entry.identity);
|
|
113
|
+
}
|
|
114
|
+
function assertSnapshot(entry, uid) {
|
|
115
|
+
const stat = inspectSnapshotIdentity(entry);
|
|
116
|
+
assertTrustedTempWorkspaceDirectory(stat, uid);
|
|
117
|
+
assertCanonicalRoot(entry);
|
|
118
|
+
}
|
|
119
|
+
function assertChain(chain, uid) {
|
|
120
|
+
for (const entry of chain) {
|
|
121
|
+
const current = inspectSnapshotIdentity(entry);
|
|
122
|
+
assertTrustedTempWorkspaceDirectory(current, uid);
|
|
123
|
+
}
|
|
124
|
+
const last = chain[chain.length - 1];
|
|
125
|
+
assertCanonicalRoot(last);
|
|
126
|
+
}
|
|
127
|
+
function canonicalAncestry(root) {
|
|
128
|
+
const ancestry = [];
|
|
129
|
+
for (let current = root;; current = path.dirname(current)) {
|
|
130
|
+
ancestry.push(current);
|
|
131
|
+
if (path.dirname(current) === current)
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
return ancestry.reverse();
|
|
135
|
+
}
|
|
136
|
+
function exactIdentityMatches(current, expected) {
|
|
137
|
+
if (current.dev !== expected.dev || current.ino !== expected.ino)
|
|
138
|
+
identityMismatch();
|
|
139
|
+
}
|
|
140
|
+
function associateTempWorkspaceRoot(entry, ownerUid, descriptorFd) {
|
|
141
|
+
const stat = inspectTempWorkspaceDescriptorIdentitySync(descriptorFd, entry.identity, entry.numericIdentity);
|
|
142
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
143
|
+
throw new FsSafeError("not-file", "temp workspace cleanup parent must be a real directory");
|
|
144
|
+
}
|
|
145
|
+
assertTrustedTempWorkspaceDirectory(stat, ownerUid);
|
|
146
|
+
}
|
|
147
|
+
function discoverMissingTempWorkspaceAncestor(ancestor, missing, missingError) {
|
|
148
|
+
// The caller already classified the first ENOENT. Keep this handoff out of
|
|
149
|
+
// the probe catch so neither it nor a later failure is classified twice.
|
|
150
|
+
const initialParent = path.dirname(ancestor);
|
|
151
|
+
if (initialParent === ancestor)
|
|
152
|
+
throw missingError;
|
|
153
|
+
missing.push(path.basename(ancestor));
|
|
154
|
+
ancestor = initialParent;
|
|
155
|
+
for (;;) {
|
|
156
|
+
try {
|
|
157
|
+
// Only subsequent non-volume-root probes may return a missing receipt.
|
|
158
|
+
const parent = path.dirname(ancestor);
|
|
159
|
+
const allowMissing = parent !== ancestor;
|
|
160
|
+
assertNoWindowsPathAlias(ancestor, "filesystem");
|
|
161
|
+
const operationPath = pathForWindowsFilesystem(ancestor);
|
|
162
|
+
const stat = allowMissing
|
|
163
|
+
? fsSync.lstatSync(operationPath, { bigint: true, throwIfNoEntry: false })
|
|
164
|
+
: fsSync.lstatSync(operationPath, { bigint: true });
|
|
165
|
+
if (allowMissing && stat === undefined) {
|
|
166
|
+
missing.push(path.basename(ancestor));
|
|
167
|
+
ancestor = parent;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
return { ancestor, initial: copyExactDirectoryObservation(stat) };
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
if (error.code !== "ENOENT")
|
|
174
|
+
throw error;
|
|
175
|
+
const parent = path.dirname(ancestor);
|
|
176
|
+
if (parent === ancestor)
|
|
177
|
+
throw error;
|
|
178
|
+
missing.push(path.basename(ancestor));
|
|
179
|
+
ancestor = parent;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function rootPlan(rootDir) {
|
|
184
|
+
assertNoWindowsPathAlias(rootDir, "filesystem");
|
|
185
|
+
const ownerUid = effectiveOwner();
|
|
186
|
+
let ancestor = resolvePathPreservingWindowsRoot(rootDir);
|
|
187
|
+
assertNoWindowsPathAlias(ancestor, "filesystem");
|
|
188
|
+
const missing = [];
|
|
189
|
+
let initial;
|
|
190
|
+
try {
|
|
191
|
+
initial = copyExactDirectoryObservation(fsSync.lstatSync(pathForWindowsFilesystem(ancestor), { bigint: true }));
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
if (error.code !== "ENOENT")
|
|
195
|
+
throw error;
|
|
196
|
+
({ ancestor, initial } = discoverMissingTempWorkspaceAncestor(ancestor, missing, error));
|
|
197
|
+
}
|
|
198
|
+
missing.reverse();
|
|
199
|
+
// Keep the historical support for a caller-approved root alias and standard
|
|
200
|
+
// system aliases such as macOS /var. A dangling alias fails canonicalization.
|
|
201
|
+
const observedAncestor = ancestor;
|
|
202
|
+
ancestor = canonicalTempWorkspacePath(observedAncestor);
|
|
203
|
+
const existingCanonicalRoot = missing.length === 0 && observedAncestor === ancestor &&
|
|
204
|
+
!initial.symbolicLink && initial.directory && hasCompleteExactIdentity(initial);
|
|
205
|
+
if (existingCanonicalRoot) {
|
|
206
|
+
const discovery = snapshotFromExactObservation(ancestor, ownerUid, ancestor, initial);
|
|
207
|
+
return {
|
|
208
|
+
admission: canonicalRootAdmission(discovery, ownerUid),
|
|
209
|
+
missing,
|
|
210
|
+
ownerUid,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
const chain = canonicalAncestry(ancestor).map((dir) => snapshot(dir, ownerUid, dir).entry);
|
|
214
|
+
// Missing-component creation needs a replay before its first mutation. When
|
|
215
|
+
// the complete root already exists through an alias, retain the historical
|
|
216
|
+
// guarded route because discovery and mutation use different path spellings.
|
|
217
|
+
if (missing.length > 0)
|
|
218
|
+
assertChain(chain, ownerUid);
|
|
219
|
+
return { chain, missing, ownerUid };
|
|
220
|
+
}
|
|
221
|
+
function rootAdmission(chain, ownerUid) {
|
|
222
|
+
// Bind the immediate parent now: missing-component creation appends to chain.
|
|
223
|
+
const current = chain[chain.length - 1];
|
|
224
|
+
return {
|
|
225
|
+
dir: current.dir,
|
|
226
|
+
identity: current.identity,
|
|
227
|
+
ownerUid,
|
|
228
|
+
realPath: current.realPath,
|
|
229
|
+
retainCleanupParent: (descriptorFd) => {
|
|
230
|
+
assertSnapshot(current, ownerUid);
|
|
231
|
+
associateTempWorkspaceRoot(current, ownerUid, descriptorFd);
|
|
232
|
+
},
|
|
233
|
+
// The guarded route associated the cleanup parent while retaining it.
|
|
234
|
+
// A native capability probe therefore needs no additional observation.
|
|
235
|
+
prepareCleanupProbe: () => { },
|
|
236
|
+
prepareChildCreation: () => { assertChain(chain, ownerUid); },
|
|
237
|
+
assertCurrent: () => { assertSnapshot(current, ownerUid); },
|
|
238
|
+
assertAncestry: () => { assertChain(chain, ownerUid); },
|
|
239
|
+
associateCurrent: (descriptorFd) => {
|
|
240
|
+
assertSnapshot(current, ownerUid);
|
|
241
|
+
associateTempWorkspaceRoot(current, ownerUid, descriptorFd);
|
|
242
|
+
},
|
|
243
|
+
associateAncestry: (descriptorFd) => {
|
|
244
|
+
assertChain(chain, ownerUid);
|
|
245
|
+
associateTempWorkspaceRoot(current, ownerUid, descriptorFd);
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
function canonicalRootAdmission(discovery, ownerUid) {
|
|
250
|
+
let admittedChain;
|
|
251
|
+
const chain = () => {
|
|
252
|
+
if (!admittedChain) {
|
|
253
|
+
throw new FsSafeError("path-mismatch", "temp workspace root has not completed creation admission");
|
|
254
|
+
}
|
|
255
|
+
return admittedChain;
|
|
256
|
+
};
|
|
257
|
+
return {
|
|
258
|
+
dir: discovery.dir,
|
|
259
|
+
identity: discovery.identity,
|
|
260
|
+
ownerUid,
|
|
261
|
+
realPath: discovery.realPath,
|
|
262
|
+
// Merely opening the cleanup-parent descriptor grants no authority. The
|
|
263
|
+
// exact association is completed only at a bounded probe or the mutation
|
|
264
|
+
// boundary below.
|
|
265
|
+
retainCleanupParent: () => { },
|
|
266
|
+
prepareCleanupProbe: (descriptorFd) => {
|
|
267
|
+
// A native feature probe may inspect only a descriptor associated with
|
|
268
|
+
// the originally discovered root. This remains provisional: the named
|
|
269
|
+
// root and complete ancestry are freshly admitted before mutation.
|
|
270
|
+
assertCanonicalRoot(discovery);
|
|
271
|
+
associateTempWorkspaceRoot(discovery, ownerUid, descriptorFd);
|
|
272
|
+
},
|
|
273
|
+
prepareChildCreation: (descriptorFd) => {
|
|
274
|
+
const ancestry = canonicalAncestry(discovery.dir);
|
|
275
|
+
const candidate = ancestry.map((dir, index) => {
|
|
276
|
+
if (!TEMP_WORKSPACE_NUMERIC_IDENTITY_REPLAY || index !== ancestry.length - 1) {
|
|
277
|
+
return snapshot(dir, ownerUid, dir).entry;
|
|
278
|
+
}
|
|
279
|
+
const current = inspectSnapshotIdentity(discovery);
|
|
280
|
+
assertTrustedTempWorkspaceDirectory(current, ownerUid);
|
|
281
|
+
return discovery;
|
|
282
|
+
});
|
|
283
|
+
const current = candidate[candidate.length - 1];
|
|
284
|
+
exactIdentityMatches(current.identity, discovery.identity);
|
|
285
|
+
assertCanonicalRoot(current);
|
|
286
|
+
if (descriptorFd !== undefined) {
|
|
287
|
+
associateTempWorkspaceRoot(current, ownerUid, descriptorFd);
|
|
288
|
+
}
|
|
289
|
+
// Do not expose even the ancestry receipts until every named and
|
|
290
|
+
// descriptor association at this pre-mutation boundary has succeeded.
|
|
291
|
+
admittedChain = candidate;
|
|
292
|
+
},
|
|
293
|
+
assertCurrent: () => {
|
|
294
|
+
const currentChain = chain();
|
|
295
|
+
assertSnapshot(currentChain[currentChain.length - 1], ownerUid);
|
|
296
|
+
},
|
|
297
|
+
assertAncestry: () => { assertChain(chain(), ownerUid); },
|
|
298
|
+
associateCurrent: (descriptorFd) => {
|
|
299
|
+
const currentChain = chain();
|
|
300
|
+
const current = currentChain[currentChain.length - 1];
|
|
301
|
+
assertSnapshot(current, ownerUid);
|
|
302
|
+
associateTempWorkspaceRoot(current, ownerUid, descriptorFd);
|
|
303
|
+
},
|
|
304
|
+
associateAncestry: (descriptorFd) => {
|
|
305
|
+
const currentChain = chain();
|
|
306
|
+
assertChain(currentChain, ownerUid);
|
|
307
|
+
associateTempWorkspaceRoot(currentChain[currentChain.length - 1], ownerUid, descriptorFd);
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
export function admitExistingTempWorkspaceRoot(rootDir) {
|
|
312
|
+
const { admission, chain, missing, ownerUid } = rootPlan(rootDir);
|
|
313
|
+
if (missing.length > 0) {
|
|
314
|
+
throw new FsSafeError("helper-unavailable", "temp file cleanup parent is unavailable");
|
|
315
|
+
}
|
|
316
|
+
return admission ?? rootAdmission(chain, ownerUid);
|
|
317
|
+
}
|
|
318
|
+
export async function admitTempWorkspaceRoot(rootDir) {
|
|
319
|
+
const { admission, chain, missing, ownerUid } = rootPlan(rootDir);
|
|
320
|
+
if (admission)
|
|
321
|
+
return admission;
|
|
322
|
+
const guardedChain = chain;
|
|
323
|
+
for (const segment of missing) {
|
|
324
|
+
const parent = rootAdmission(guardedChain, ownerUid);
|
|
325
|
+
const dir = path.join(parent.dir, segment);
|
|
326
|
+
assertNoWindowsPathAlias(dir, "filesystem");
|
|
327
|
+
parent.assertCurrent();
|
|
328
|
+
let created = false;
|
|
329
|
+
try {
|
|
330
|
+
await fs.mkdir(dir, { mode: 0o700 });
|
|
331
|
+
created = true;
|
|
332
|
+
}
|
|
333
|
+
catch (error) {
|
|
334
|
+
if (error.code !== "EEXIST")
|
|
335
|
+
throw error;
|
|
336
|
+
}
|
|
337
|
+
parent.assertCurrent();
|
|
338
|
+
const observed = snapshot(dir, ownerUid);
|
|
339
|
+
if (created) {
|
|
340
|
+
const modeInitialization = admitTempWorkspaceChild(dir, observed.stat, parent, 0o700);
|
|
341
|
+
if (modeInitialization)
|
|
342
|
+
await modeInitialization;
|
|
343
|
+
}
|
|
344
|
+
guardedChain.push(observed.entry);
|
|
345
|
+
}
|
|
346
|
+
return rootAdmission(guardedChain, ownerUid);
|
|
347
|
+
}
|
|
348
|
+
export function admitTempWorkspaceRootSync(rootDir) {
|
|
349
|
+
const { admission, chain, missing, ownerUid } = rootPlan(rootDir);
|
|
350
|
+
if (admission)
|
|
351
|
+
return admission;
|
|
352
|
+
const guardedChain = chain;
|
|
353
|
+
for (const segment of missing) {
|
|
354
|
+
const parent = rootAdmission(guardedChain, ownerUid);
|
|
355
|
+
const dir = path.join(parent.dir, segment);
|
|
356
|
+
assertNoWindowsPathAlias(dir, "filesystem");
|
|
357
|
+
parent.assertCurrent();
|
|
358
|
+
let created = false;
|
|
359
|
+
try {
|
|
360
|
+
fsSync.mkdirSync(dir, { mode: 0o700 });
|
|
361
|
+
created = true;
|
|
362
|
+
}
|
|
363
|
+
catch (error) {
|
|
364
|
+
if (error.code !== "EEXIST")
|
|
365
|
+
throw error;
|
|
366
|
+
}
|
|
367
|
+
parent.assertCurrent();
|
|
368
|
+
const observed = snapshot(dir, ownerUid);
|
|
369
|
+
if (created)
|
|
370
|
+
admitTempWorkspaceChildSync(dir, observed.stat, parent, 0o700);
|
|
371
|
+
guardedChain.push(observed.entry);
|
|
372
|
+
}
|
|
373
|
+
return rootAdmission(guardedChain, ownerUid);
|
|
374
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { BigIntStats, Stats } from "node:fs";
|
|
2
|
+
import type { TempWorkspaceRootAdmission } from "./temp-workspace-admission.js";
|
|
3
|
+
import type { TempWorkspaceRetainedChild } from "./temp-workspace-descriptor.js";
|
|
4
|
+
import { type TempWorkspaceIdentityStat } from "./temp-workspace-identity.js";
|
|
5
|
+
export declare function assertTempWorkspaceChildState(stat: BigIntStats | Stats, ownerUid: number | undefined): void;
|
|
6
|
+
export declare function validateInitialTempWorkspaceChild(stat: BigIntStats, ownerUid: number | undefined, mode: number): boolean;
|
|
7
|
+
export declare function childHasRequestedMode(stat: BigIntStats | Stats, mode: number): boolean;
|
|
8
|
+
export declare function admitTempWorkspaceChild(dir: string, expected: BigIntStats, parent: TempWorkspaceRootAdmission, mode: number): Promise<void> | undefined;
|
|
9
|
+
export declare function admitTempWorkspaceChildSync(dir: string, expected: BigIntStats, parent: TempWorkspaceRootAdmission, mode: number): void;
|
|
10
|
+
export declare function admitRetainedTempWorkspaceChild(retained: TempWorkspaceRetainedChild, needsModeInitialization: boolean, parent: TempWorkspaceRootAdmission, mode: number): Promise<void> | undefined;
|
|
11
|
+
export declare function admitRetainedTempWorkspaceChildSync(retained: TempWorkspaceRetainedChild, needsModeInitialization: boolean, parent: TempWorkspaceRootAdmission, mode: number): void;
|
|
12
|
+
export declare function validateAdmittedTempWorkspaceChild(current: TempWorkspaceIdentityStat, ownerUid: number | undefined, mode: number): TempWorkspaceIdentityStat;
|
|
13
|
+
//# sourceMappingURL=temp-workspace-child-admission.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"temp-workspace-child-admission.d.ts","sourceRoot":"","sources":["../src/temp-workspace-child-admission.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAIlD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAChF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,gCAAgC,CAAC;AACjF,OAAO,EAEL,KAAK,yBAAyB,EAC/B,MAAM,8BAA8B,CAAC;AAKtC,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,WAAW,GAAG,KAAK,EACzB,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B,IAAI,CAaN;AAED,wBAAgB,iCAAiC,CAC/C,IAAI,EAAE,WAAW,EACjB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,IAAI,EAAE,MAAM,GACX,OAAO,CAGT;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,WAAW,GAAG,KAAK,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAMtF;AA0BD,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,WAAW,EACrB,MAAM,EAAE,0BAA0B,EAClC,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,GAAG,SAAS,CAG3B;AAED,wBAAgB,2BAA2B,CACzC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,WAAW,EACrB,MAAM,EAAE,0BAA0B,EAClC,IAAI,EAAE,MAAM,GACX,IAAI,CAkBN;AAED,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,0BAA0B,EACpC,uBAAuB,EAAE,OAAO,EAChC,MAAM,EAAE,0BAA0B,EAClC,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,GAAG,SAAS,CAG3B;AAED,wBAAgB,mCAAmC,CACjD,QAAQ,EAAE,0BAA0B,EACpC,uBAAuB,EAAE,OAAO,EAChC,MAAM,EAAE,0BAA0B,EAClC,IAAI,EAAE,MAAM,GACX,IAAI,CAMN;AAED,wBAAgB,kCAAkC,CAChD,OAAO,EAAE,yBAAyB,EAClC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,IAAI,EAAE,MAAM,GACX,yBAAyB,CAM3B"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { inspectDirectoryIdentitySync } from "./directory-guard.js";
|
|
2
|
+
import { pinNodeDirectoryForMode, pinNodeDirectoryForModeSync } from "./directory-mode-node.js";
|
|
3
|
+
import { FsSafeError } from "./errors.js";
|
|
4
|
+
import { TEMP_WORKSPACE_NUMERIC_IDENTITY_REPLAY, } from "./temp-workspace-identity.js";
|
|
5
|
+
import { assertTrustedTempWorkspaceDirectory } from "./temp-workspace-permissions.js";
|
|
6
|
+
const WINDOWS = process.platform === "win32";
|
|
7
|
+
export function assertTempWorkspaceChildState(stat, ownerUid) {
|
|
8
|
+
const exactIdentity = typeof stat.dev === "bigint" && typeof stat.ino === "bigint" &&
|
|
9
|
+
(!WINDOWS || (stat.dev !== 0n && stat.ino !== 0n));
|
|
10
|
+
const safeNumericIdentity = TEMP_WORKSPACE_NUMERIC_IDENTITY_REPLAY &&
|
|
11
|
+
typeof stat.dev === "number" && Number.isSafeInteger(stat.dev) && stat.dev >= 0 &&
|
|
12
|
+
typeof stat.ino === "number" && Number.isSafeInteger(stat.ino) && stat.ino >= 0;
|
|
13
|
+
if (!exactIdentity && !safeNumericIdentity) {
|
|
14
|
+
throw new FsSafeError("path-mismatch", "temp workspace child identity could not be verified");
|
|
15
|
+
}
|
|
16
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
17
|
+
throw new FsSafeError("not-file", "temp workspace child must be a real directory");
|
|
18
|
+
}
|
|
19
|
+
assertTrustedTempWorkspaceDirectory(stat, ownerUid, true);
|
|
20
|
+
}
|
|
21
|
+
export function validateInitialTempWorkspaceChild(stat, ownerUid, mode) {
|
|
22
|
+
assertTempWorkspaceChildState(stat, ownerUid);
|
|
23
|
+
return !childHasRequestedMode(stat, mode);
|
|
24
|
+
}
|
|
25
|
+
export function childHasRequestedMode(stat, mode) {
|
|
26
|
+
// Windows st_mode does not establish ACL privacy. Creation still validates
|
|
27
|
+
// the exact named object, while the supplied root's ACL remains caller trust.
|
|
28
|
+
return WINDOWS || (typeof stat.mode === "bigint"
|
|
29
|
+
? Number(stat.mode & 4095n)
|
|
30
|
+
: stat.mode & 0o7777) === (mode & 0o7777);
|
|
31
|
+
}
|
|
32
|
+
async function initializeTempWorkspaceChildMode(dir, expected, parent, mode) {
|
|
33
|
+
parent.assertCurrent();
|
|
34
|
+
const owner = await pinNodeDirectoryForMode(dir, {
|
|
35
|
+
expectedIdentity: expected,
|
|
36
|
+
ownerUid: parent.ownerUid,
|
|
37
|
+
});
|
|
38
|
+
try {
|
|
39
|
+
await owner.apply(mode, { check: parent.assertCurrent });
|
|
40
|
+
const current = inspectDirectoryIdentitySync(dir, expected);
|
|
41
|
+
assertTempWorkspaceChildState(current, parent.ownerUid);
|
|
42
|
+
if (!childHasRequestedMode(current, mode)) {
|
|
43
|
+
throw new FsSafeError("path-mismatch", "temp workspace final mode could not be verified");
|
|
44
|
+
}
|
|
45
|
+
parent.assertCurrent();
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
await owner.close();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function admitTempWorkspaceChild(dir, expected, parent, mode) {
|
|
52
|
+
if (!validateInitialTempWorkspaceChild(expected, parent.ownerUid, mode))
|
|
53
|
+
return undefined;
|
|
54
|
+
return initializeTempWorkspaceChildMode(dir, expected, parent, mode);
|
|
55
|
+
}
|
|
56
|
+
export function admitTempWorkspaceChildSync(dir, expected, parent, mode) {
|
|
57
|
+
if (!validateInitialTempWorkspaceChild(expected, parent.ownerUid, mode))
|
|
58
|
+
return;
|
|
59
|
+
parent.assertCurrent();
|
|
60
|
+
const owner = pinNodeDirectoryForModeSync(dir, {
|
|
61
|
+
expectedIdentity: expected,
|
|
62
|
+
ownerUid: parent.ownerUid,
|
|
63
|
+
});
|
|
64
|
+
try {
|
|
65
|
+
owner.apply(mode, parent.assertCurrent);
|
|
66
|
+
const current = inspectDirectoryIdentitySync(dir, expected);
|
|
67
|
+
assertTempWorkspaceChildState(current, parent.ownerUid);
|
|
68
|
+
if (!childHasRequestedMode(current, mode)) {
|
|
69
|
+
throw new FsSafeError("path-mismatch", "temp workspace final mode could not be verified");
|
|
70
|
+
}
|
|
71
|
+
parent.assertCurrent();
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
owner.close();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export function admitRetainedTempWorkspaceChild(retained, needsModeInitialization, parent, mode) {
|
|
78
|
+
if (!needsModeInitialization)
|
|
79
|
+
return undefined;
|
|
80
|
+
return retained.initializeMode(mode, parent.ownerUid, parent.assertCurrent);
|
|
81
|
+
}
|
|
82
|
+
export function admitRetainedTempWorkspaceChildSync(retained, needsModeInitialization, parent, mode) {
|
|
83
|
+
if (!needsModeInitialization) {
|
|
84
|
+
retained.discardInitialReceipt();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
retained.initializeModeSync(mode, parent.ownerUid, parent.assertCurrent);
|
|
88
|
+
}
|
|
89
|
+
export function validateAdmittedTempWorkspaceChild(current, ownerUid, mode) {
|
|
90
|
+
assertTempWorkspaceChildState(current, ownerUid);
|
|
91
|
+
if (!childHasRequestedMode(current, mode)) {
|
|
92
|
+
throw new FsSafeError("path-mismatch", "temp workspace final mode could not be verified");
|
|
93
|
+
}
|
|
94
|
+
return current;
|
|
95
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import fsSync from "node:fs";
|
|
2
|
+
import type { FileIdentityStat } from "./file-identity.js";
|
|
3
|
+
import type { TempWorkspaceRootAdmission } from "./temp-workspace-admission.js";
|
|
4
|
+
import { type TempWorkspaceIdentityStat } from "./temp-workspace-identity.js";
|
|
5
|
+
type DirectoryDescriptorAccess = "read" | "search";
|
|
6
|
+
export type RetainedDirectory = {
|
|
7
|
+
fd: number;
|
|
8
|
+
access: DirectoryDescriptorAccess;
|
|
9
|
+
receipt: Readonly<{
|
|
10
|
+
path: string;
|
|
11
|
+
realPath: string;
|
|
12
|
+
identity: Readonly<{
|
|
13
|
+
dev: bigint;
|
|
14
|
+
ino: bigint;
|
|
15
|
+
}>;
|
|
16
|
+
}>;
|
|
17
|
+
};
|
|
18
|
+
export type RetainedChildDirectory = {
|
|
19
|
+
fd: number;
|
|
20
|
+
};
|
|
21
|
+
export declare function openTempWorkspaceCleanupParent(root: string, admission: TempWorkspaceRootAdmission): RetainedDirectory;
|
|
22
|
+
/** Owns the child descriptor until final admission transfers it to cleanup. */
|
|
23
|
+
export declare class TempWorkspaceRetainedChild {
|
|
24
|
+
#private;
|
|
25
|
+
private constructor();
|
|
26
|
+
static retain(dir: string, identity: FileIdentityStat): TempWorkspaceRetainedChild;
|
|
27
|
+
static retainCreated(dir: string): {
|
|
28
|
+
retained: TempWorkspaceRetainedChild;
|
|
29
|
+
stat: fsSync.BigIntStats;
|
|
30
|
+
};
|
|
31
|
+
discardInitialReceipt(): void;
|
|
32
|
+
finalizeAdmission(ownerUid: number | undefined, mode: number): TempWorkspaceIdentityStat;
|
|
33
|
+
initializeMode(mode: number, ownerUid: number | undefined, assertParent: () => void): Promise<void>;
|
|
34
|
+
initializeModeSync(mode: number, ownerUid: number | undefined, assertParent: () => void): void;
|
|
35
|
+
get canEnumerate(): boolean;
|
|
36
|
+
ensureReadable(): boolean;
|
|
37
|
+
transfer(retainDescriptor: boolean): {
|
|
38
|
+
dir: string;
|
|
39
|
+
identity: Readonly<{
|
|
40
|
+
dev: bigint;
|
|
41
|
+
ino: bigint;
|
|
42
|
+
}>;
|
|
43
|
+
directory: RetainedChildDirectory | undefined;
|
|
44
|
+
};
|
|
45
|
+
close(): void;
|
|
46
|
+
}
|
|
47
|
+
export {};
|
|
48
|
+
//# sourceMappingURL=temp-workspace-descriptor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"temp-workspace-descriptor.d.ts","sourceRoot":"","sources":["../src/temp-workspace-descriptor.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,SAAS,CAAC;AAU7B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAMhF,OAAO,EAML,KAAK,yBAAyB,EAC/B,MAAM,8BAA8B,CAAC;AAEtC,KAAK,yBAAyB,GAAG,MAAM,GAAG,QAAQ,CAAC;AASnD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,yBAAyB,CAAC;IAClC,OAAO,EAAE,QAAQ,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,QAAQ,CAAC;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,GAAG,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KAClD,CAAC,CAAC;CACJ,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAmDpD,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,0BAA0B,GACpC,iBAAiB,CA8BnB;AAED,+EAA+E;AAC/E,qBAAa,0BAA0B;;IAWrC,OAAO,eAqBN;IAED,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,GAAG,0BAA0B,CAcjF;IAED,MAAM,CAAC,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG;QACjC,QAAQ,EAAE,0BAA0B,CAAC;QACrC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC;KAC1B,CAoBA;IAED,qBAAqB,IAAI,IAAI,CAE5B;IAUD,iBAAiB,CACf,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,IAAI,EAAE,MAAM,GACX,yBAAyB,CAsB3B;IAoEK,cAAc,CAClB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,YAAY,EAAE,MAAM,IAAI,GACvB,OAAO,CAAC,IAAI,CAAC,CAgCf;IAED,kBAAkB,CAChB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,YAAY,EAAE,MAAM,IAAI,GACvB,IAAI,CAsBN;IAED,IAAI,YAAY,IAAI,OAAO,CAE1B;IAED,cAAc,IAAI,OAAO,CAmDxB;IAED,QAAQ,CAAC,gBAAgB,EAAE,OAAO,GAAG;QACnC,GAAG,EAAE,MAAM,CAAC;QACZ,QAAQ,EAAE,QAAQ,CAAC;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,GAAG,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QACjD,SAAS,EAAE,sBAAsB,GAAG,SAAS,CAAC;KAC/C,CAiCA;IAED,KAAK,IAAI,IAAI,CAOZ;CACF"}
|