@openclaw/fs-safe 0.12.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 +61 -0
- package/README.md +12 -7
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +32 -11
- package/dist/advanced.d.ts +2 -0
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +2 -0
- package/dist/archive-input.d.ts.map +1 -1
- package/dist/archive-input.js +6 -2
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +15 -2
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +7 -19
- package/dist/archive-plan.js +1 -1
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +18 -13
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +43 -10
- package/dist/archive-tar-inspect.d.ts.map +1 -1
- package/dist/archive-tar-inspect.js +4 -1
- package/dist/archive-zip-admission.d.ts +1 -1
- package/dist/archive-zip-admission.d.ts.map +1 -1
- package/dist/archive-zip-admission.js +2 -2
- package/dist/archive-zip-directory.d.ts +3 -0
- package/dist/archive-zip-directory.d.ts.map +1 -1
- package/dist/archive-zip-directory.js +13 -2
- package/dist/archive-zip-loader.d.ts +3 -2
- package/dist/archive-zip-loader.d.ts.map +1 -1
- package/dist/archive-zip-loader.js +30 -4
- package/dist/archive-zip-manifest.d.ts +5 -0
- package/dist/archive-zip-manifest.d.ts.map +1 -0
- package/dist/archive-zip-manifest.js +22 -0
- package/dist/archive-zip-names.d.ts +6 -1
- package/dist/archive-zip-names.d.ts.map +1 -1
- package/dist/archive-zip-names.js +35 -14
- package/dist/archive-zip-preflight.d.ts.map +1 -1
- package/dist/archive-zip-preflight.js +3 -2
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +9 -4
- package/dist/darwin-acl.d.ts +4 -0
- package/dist/darwin-acl.d.ts.map +1 -0
- package/dist/darwin-acl.js +24 -0
- package/dist/deny-mutations.d.ts.map +1 -1
- package/dist/deny-mutations.js +8 -2
- package/dist/directory-durability.d.ts.map +1 -1
- package/dist/directory-durability.js +67 -23
- package/dist/directory-entry-path.d.ts +3 -0
- package/dist/directory-entry-path.d.ts.map +1 -0
- package/dist/directory-entry-path.js +21 -0
- package/dist/directory-guard.d.ts +17 -1
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-guard.js +134 -48
- package/dist/directory-mode-node.d.ts +12 -0
- package/dist/directory-mode-node.d.ts.map +1 -1
- package/dist/directory-mode-node.js +102 -4
- package/dist/effective-uid.d.ts +2 -0
- package/dist/effective-uid.d.ts.map +1 -0
- package/dist/effective-uid.js +25 -0
- package/dist/file-handle-transfer.d.ts.map +1 -1
- package/dist/file-handle-transfer.js +98 -27
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +3 -0
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +36 -6
- package/dist/file-lock.d.ts.map +1 -1
- package/dist/file-lock.js +29 -9
- package/dist/file-observation.d.ts +1 -1
- package/dist/file-observation.d.ts.map +1 -1
- package/dist/file-store-boundary.d.ts +6 -2
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +20 -65
- package/dist/file-store-copy-source.d.ts +5 -0
- package/dist/file-store-copy-source.d.ts.map +1 -0
- package/dist/file-store-copy-source.js +31 -0
- package/dist/file-store-path.d.ts.map +1 -1
- package/dist/file-store-path.js +4 -1
- package/dist/file-store-sync-directory.d.ts +16 -0
- package/dist/file-store-sync-directory.d.ts.map +1 -0
- package/dist/file-store-sync-directory.js +349 -0
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +11 -28
- package/dist/filename.d.ts.map +1 -1
- package/dist/filename.js +65 -20
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +3 -2
- package/dist/guarded-mkdir.d.ts +8 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +176 -27
- package/dist/guest.d.ts.map +1 -1
- package/dist/guest.js +4 -1
- package/dist/home-dir.d.ts.map +1 -1
- package/dist/home-dir.js +73 -10
- package/dist/install-path.d.ts.map +1 -1
- package/dist/install-path.js +54 -19
- package/dist/json-durable-queue-ownership.d.ts +6 -0
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +89 -42
- package/dist/json-durable-queue-paths.d.ts +11 -0
- package/dist/json-durable-queue-paths.d.ts.map +1 -0
- package/dist/json-durable-queue-paths.js +42 -0
- package/dist/json-durable-queue-read.d.ts.map +1 -1
- package/dist/json-durable-queue-read.js +2 -0
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +50 -58
- package/dist/json-store.d.ts.map +1 -1
- package/dist/json-store.js +5 -1
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +54 -22
- package/dist/local-file-access.d.ts.map +1 -1
- package/dist/local-file-access.js +4 -0
- package/dist/local-file-descriptor.d.ts +17 -0
- package/dist/local-file-descriptor.d.ts.map +1 -0
- package/dist/local-file-descriptor.js +84 -0
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +35 -7
- package/dist/move-path-cleanup.d.ts +2 -0
- package/dist/move-path-cleanup.d.ts.map +1 -1
- package/dist/move-path-cleanup.js +44 -18
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +31 -6
- package/dist/native-binding.d.ts +14 -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.map +1 -1
- package/dist/path-scope-lexical.js +18 -8
- package/dist/path-segment-route.d.ts +7 -0
- package/dist/path-segment-route.d.ts.map +1 -0
- package/dist/path-segment-route.js +24 -0
- package/dist/path-suffix-aliases.d.ts +10 -0
- package/dist/path-suffix-aliases.d.ts.map +1 -0
- package/dist/path-suffix-aliases.js +386 -0
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +16 -5
- package/dist/permissions-windows.d.ts.map +1 -1
- package/dist/permissions-windows.js +14 -3
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +37 -8
- package/dist/pinned-mutation-admission.d.ts +25 -0
- package/dist/pinned-mutation-admission.d.ts.map +1 -0
- package/dist/pinned-mutation-admission.js +425 -0
- package/dist/pinned-mutation-observation.d.ts +34 -0
- package/dist/pinned-mutation-observation.d.ts.map +1 -0
- package/dist/pinned-mutation-observation.js +142 -0
- package/dist/pinned-mutation-shared-route.d.ts +24 -0
- package/dist/pinned-mutation-shared-route.d.ts.map +1 -0
- package/dist/pinned-mutation-shared-route.js +70 -0
- package/dist/pinned-open.d.ts +6 -0
- package/dist/pinned-open.d.ts.map +1 -1
- package/dist/pinned-open.js +28 -10
- package/dist/pinned-write-types.d.ts +75 -0
- package/dist/pinned-write-types.d.ts.map +1 -0
- package/dist/pinned-write-types.js +1 -0
- package/dist/pinned-write.d.ts +7 -33
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +151 -17
- package/dist/private-directory.d.ts.map +1 -1
- package/dist/private-directory.js +2 -0
- package/dist/private-producer-handoff.d.ts +16 -0
- package/dist/private-producer-handoff.d.ts.map +1 -0
- package/dist/private-producer-handoff.js +272 -0
- package/dist/private-temp-workspace.d.ts +2 -39
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +183 -77
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +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 +4 -0
- package/dist/root-boundary.d.ts.map +1 -1
- package/dist/root-boundary.js +6 -1
- package/dist/root-context.d.ts +12 -1
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +57 -11
- package/dist/root-directory-creation.d.ts +13 -0
- package/dist/root-directory-creation.d.ts.map +1 -0
- package/dist/root-directory-creation.js +212 -0
- package/dist/root-directory-list.d.ts +16 -4
- package/dist/root-directory-list.d.ts.map +1 -1
- package/dist/root-directory-list.js +180 -39
- package/dist/root-directory.d.ts +23 -0
- package/dist/root-directory.d.ts.map +1 -0
- package/dist/root-directory.js +141 -0
- package/dist/root-errors.d.ts.map +1 -1
- package/dist/root-errors.js +12 -1
- package/dist/root-file-final-admission.d.ts +21 -0
- package/dist/root-file-final-admission.d.ts.map +1 -0
- package/dist/root-file-final-admission.js +83 -0
- package/dist/root-file.d.ts.map +1 -1
- package/dist/root-file.js +103 -24
- package/dist/root-impl.d.ts +1 -0
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +289 -317
- package/dist/root-move-noreplace.d.ts +14 -0
- package/dist/root-move-noreplace.d.ts.map +1 -0
- package/dist/root-move-noreplace.js +202 -0
- package/dist/root-observed-path.d.ts +9 -0
- package/dist/root-observed-path.d.ts.map +1 -0
- package/dist/root-observed-path.js +95 -0
- package/dist/root-path-errors.d.ts +12 -0
- package/dist/root-path-errors.d.ts.map +1 -0
- package/dist/root-path-errors.js +13 -0
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +53 -20
- package/dist/root-path-observation.d.ts +63 -0
- package/dist/root-path-observation.d.ts.map +1 -0
- package/dist/root-path-observation.js +180 -0
- package/dist/root-path-stat.d.ts +5 -0
- package/dist/root-path-stat.d.ts.map +1 -0
- package/dist/root-path-stat.js +101 -0
- package/dist/root-path-symlink.d.ts.map +1 -1
- package/dist/root-path-symlink.js +23 -4
- package/dist/root-path.d.ts +11 -0
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +215 -53
- package/dist/root-paths-lexical.d.ts +9 -0
- package/dist/root-paths-lexical.d.ts.map +1 -0
- package/dist/root-paths-lexical.js +22 -0
- package/dist/root-paths.d.ts +3 -25
- package/dist/root-paths.d.ts.map +1 -1
- package/dist/root-paths.js +77 -154
- package/dist/root-read-admission.d.ts +26 -0
- package/dist/root-read-admission.d.ts.map +1 -0
- package/dist/root-read-admission.js +94 -0
- package/dist/root-remove-identity.d.ts +15 -0
- package/dist/root-remove-identity.d.ts.map +1 -0
- package/dist/root-remove-identity.js +89 -0
- package/dist/root-remove-receipt.d.ts +17 -0
- package/dist/root-remove-receipt.d.ts.map +1 -0
- package/dist/root-remove-receipt.js +37 -0
- package/dist/root-remove.d.ts +2 -1
- package/dist/root-remove.d.ts.map +1 -1
- package/dist/root-remove.js +172 -10
- package/dist/root-write-admission.d.ts +68 -0
- package/dist/root-write-admission.d.ts.map +1 -0
- package/dist/root-write-admission.js +322 -0
- package/dist/root-write-compatibility.d.ts +13 -0
- package/dist/root-write-compatibility.d.ts.map +1 -0
- package/dist/root-write-compatibility.js +74 -0
- package/dist/root-write-complete-parent.d.ts +42 -0
- package/dist/root-write-complete-parent.d.ts.map +1 -0
- package/dist/root-write-complete-parent.js +195 -0
- package/dist/root-write-publication.d.ts +24 -0
- package/dist/root-write-publication.d.ts.map +1 -0
- package/dist/root-write-publication.js +85 -0
- package/dist/root-write-verification.d.ts.map +1 -1
- package/dist/root-write-verification.js +6 -4
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +62 -24
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +18 -13
- package/dist/secret-read-policy.d.ts.map +1 -1
- package/dist/secret-read-policy.js +5 -1
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +92 -7
- package/dist/secure-temp-dir.d.ts +6 -3
- package/dist/secure-temp-dir.d.ts.map +1 -1
- package/dist/secure-temp-dir.js +121 -101
- package/dist/secure-temp-repair.d.ts +35 -0
- package/dist/secure-temp-repair.d.ts.map +1 -0
- package/dist/secure-temp-repair.js +106 -0
- package/dist/sibling-staged-file.d.ts +3 -2
- package/dist/sibling-staged-file.d.ts.map +1 -1
- package/dist/sibling-staged-file.js +179 -55
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +27 -13
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +44 -14
- package/dist/sidecar-lock-root.d.ts.map +1 -1
- package/dist/sidecar-lock-root.js +4 -2
- package/dist/staged-directory.d.ts +14 -5
- package/dist/staged-directory.d.ts.map +1 -1
- package/dist/staged-directory.js +54 -3
- package/dist/standalone-publication-path.d.ts +2 -0
- package/dist/standalone-publication-path.d.ts.map +1 -0
- package/dist/standalone-publication-path.js +6 -0
- package/dist/stat-observation.d.ts +11 -0
- package/dist/stat-observation.d.ts.map +1 -0
- package/dist/stat-observation.js +64 -0
- package/dist/strict-file-identity.d.ts.map +1 -1
- package/dist/strict-file-identity.js +39 -2
- package/dist/symlink-parents.d.ts.map +1 -1
- package/dist/symlink-parents.js +7 -2
- package/dist/temp-target.d.ts +2 -0
- package/dist/temp-target.d.ts.map +1 -1
- package/dist/temp-target.js +130 -3
- package/dist/temp-workspace-admission.d.ts +22 -0
- package/dist/temp-workspace-admission.d.ts.map +1 -0
- package/dist/temp-workspace-admission.js +374 -0
- package/dist/temp-workspace-child-admission.d.ts +13 -0
- package/dist/temp-workspace-child-admission.d.ts.map +1 -0
- package/dist/temp-workspace-child-admission.js +95 -0
- package/dist/temp-workspace-descriptor.d.ts +48 -0
- package/dist/temp-workspace-descriptor.d.ts.map +1 -0
- package/dist/temp-workspace-descriptor.js +363 -0
- package/dist/temp-workspace-identity.d.ts +15 -0
- package/dist/temp-workspace-identity.d.ts.map +1 -0
- package/dist/temp-workspace-identity.js +41 -0
- package/dist/temp-workspace-owner.d.ts +7 -6
- package/dist/temp-workspace-owner.d.ts.map +1 -1
- package/dist/temp-workspace-owner.js +121 -61
- package/dist/temp-workspace-permissions.d.ts +4 -0
- package/dist/temp-workspace-permissions.d.ts.map +1 -0
- package/dist/temp-workspace-permissions.js +32 -0
- package/dist/temp-workspace-types.d.ts +40 -0
- package/dist/temp-workspace-types.d.ts.map +1 -0
- package/dist/temp-workspace-types.js +1 -0
- package/dist/temp.d.ts +1 -1
- package/dist/temp.d.ts.map +1 -1
- package/dist/temp.js +1 -1
- package/dist/test-hooks.d.ts +7 -0
- package/dist/test-hooks.d.ts.map +1 -1
- package/dist/text-atomic.d.ts.map +1 -1
- package/dist/text-atomic.js +3 -1
- package/dist/trash.d.ts.map +1 -1
- package/dist/trash.js +54 -8
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +9 -6
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +5 -0
- package/dist/windows-path-alias.d.ts +39 -0
- package/dist/windows-path-alias.d.ts.map +1 -0
- package/dist/windows-path-alias.js +153 -0
- package/docs/advanced.md +25 -0
- package/docs/archive.md +23 -2
- package/docs/atomic.md +27 -3
- package/docs/copy.md +3 -0
- package/docs/durability.md +14 -0
- package/docs/errors.md +14 -6
- package/docs/file-store.md +24 -3
- package/docs/filename.md +14 -7
- package/docs/install-path.md +2 -2
- package/docs/install.md +8 -7
- package/docs/json-store.md +5 -1
- package/docs/json.md +11 -0
- package/docs/mutation-policy-proof.md +65 -0
- package/docs/native-helper.md +26 -9
- package/docs/native.md +32 -8
- package/docs/output.md +33 -11
- package/docs/path-prefix.md +64 -0
- package/docs/path-suffix-aliases.md +159 -0
- package/docs/path.md +11 -0
- package/docs/private-file-store.md +14 -0
- package/docs/public-api.md +9 -0
- package/docs/quickstart.md +1 -1
- package/docs/reading.md +8 -1
- package/docs/root.md +18 -3
- package/docs/secret-file.md +3 -0
- package/docs/secure-file.md +6 -2
- package/docs/security-model.md +75 -8
- package/docs/sidecar-lock.md +28 -1
- package/docs/store.md +5 -1
- package/docs/temp.md +260 -36
- package/docs/test-hooks.md +14 -0
- package/docs/writing.md +38 -8
- package/package.json +10 -10
|
@@ -50,16 +50,278 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
50
50
|
var e = new Error(message);
|
|
51
51
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
52
|
});
|
|
53
|
-
import fsSync from "node:fs";
|
|
54
|
-
import fs from "node:fs/promises";
|
|
53
|
+
import fsSync, {} from "node:fs";
|
|
55
54
|
import path from "node:path";
|
|
56
55
|
import { inspectDirectoryIdentity } from "./directory-guard.js";
|
|
57
56
|
import { FsSafeError } from "./errors.js";
|
|
58
|
-
import { runPinnedWriteWindows
|
|
57
|
+
import { runPinnedWriteWindows } from "./native-pinned-write-windows.js";
|
|
58
|
+
import { openNativeParentAdmission, openNativeRootAdmission } from "./native-parent-admission.js";
|
|
59
59
|
import { assertNativeStaging, writeNativeStage } from "./native-staged-file.js";
|
|
60
|
-
import { describeStagedDirectory,
|
|
60
|
+
import { assertPolicyStagedDirectoryCurrent, assertStagedDirectoryCurrent, describePolicyStagedDirectory, describeStagedDirectory, refreshPolicyStagedDirectoryObservation, } from "./staged-directory.js";
|
|
61
61
|
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
62
62
|
import { realpathSync } from "./realpath.js";
|
|
63
|
+
import { isNotFoundPathError, isSymlinkOpenError } from "./path.js";
|
|
64
|
+
import { checkedMutationDirectory, } from "./pinned-mutation-observation.js";
|
|
65
|
+
import { createPathSegmentRoute, joinPathSegmentRoute } from "./path-segment-route.js";
|
|
66
|
+
function relativeParentSegments(relativeParentPath) {
|
|
67
|
+
return relativeParentPath.split("/").filter(Boolean);
|
|
68
|
+
}
|
|
69
|
+
function prospectiveTargetPath(parentPath, parentRoute, offset, basename) {
|
|
70
|
+
return joinPathSegmentRoute(parentPath, parentRoute, offset, basename);
|
|
71
|
+
}
|
|
72
|
+
function sameAbsolutePath(left, right) {
|
|
73
|
+
return path.relative(path.resolve(left), path.resolve(right)) === "";
|
|
74
|
+
}
|
|
75
|
+
function assertDirectChildBasename(basename) {
|
|
76
|
+
if (!basename || basename === "." || basename === ".." || basename.includes("/") ||
|
|
77
|
+
basename.includes("\0") || (process.platform === "win32" && basename.includes("\\"))) {
|
|
78
|
+
throw new FsSafeError("invalid-path", "native parent creation requires one direct-child basename");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function mkdirPolicyChild(binding, parentFd, basename, mode) {
|
|
82
|
+
assertDirectChildBasename(basename);
|
|
83
|
+
const mkdirChild = binding.mkdirChildBeneath;
|
|
84
|
+
if (typeof mkdirChild === "function") {
|
|
85
|
+
const created = mkdirChild.call(binding, parentFd, basename, mode);
|
|
86
|
+
if (typeof created === "boolean")
|
|
87
|
+
return created;
|
|
88
|
+
}
|
|
89
|
+
// Old or malformed optional helpers still execute through the established
|
|
90
|
+
// recursive primitive, but cannot provide exclusive-creation provenance.
|
|
91
|
+
binding.mkdirBeneath(parentFd, basename, mode);
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
async function authorizePinnedMutation(params, request) {
|
|
95
|
+
return await params.mutationAdmission?.authorize(Object.freeze(request));
|
|
96
|
+
}
|
|
97
|
+
function normalizePolicyParentOpenError(error, params) {
|
|
98
|
+
if (!isSymlinkOpenError(error))
|
|
99
|
+
return error;
|
|
100
|
+
return params.mutationAdmission?.rejectParentSymlinks
|
|
101
|
+
? new FsSafeError("symlink", "symlink path component not allowed", {
|
|
102
|
+
cause: error instanceof Error ? error : undefined,
|
|
103
|
+
})
|
|
104
|
+
: new FsSafeError("path-mismatch", "native write parent changed during policy admission", {
|
|
105
|
+
cause: error instanceof Error ? error : undefined,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
async function describePosixParent(parentFd, pathname) {
|
|
109
|
+
const parentPath = realpathSync.native(pathname);
|
|
110
|
+
const directory = describeStagedDirectory(parentFd, parentPath);
|
|
111
|
+
const parentPathStat = await inspectDirectoryIdentity(parentPath, inspectFileIdentitySync(() => fsSync.fstatSync(parentFd, { bigint: true })));
|
|
112
|
+
return {
|
|
113
|
+
parentFd,
|
|
114
|
+
parentPath,
|
|
115
|
+
directory,
|
|
116
|
+
parentPathStat,
|
|
117
|
+
observation: checkedMutationDirectory(parentPath, directory.realPath, parentPathStat),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function describePolicyPosixParent(parentFd, pathname) {
|
|
121
|
+
const policyDirectory = describePolicyStagedDirectory(parentFd, pathname);
|
|
122
|
+
return {
|
|
123
|
+
parentFd,
|
|
124
|
+
parentPath: policyDirectory.directory.realPath,
|
|
125
|
+
directory: policyDirectory.directory,
|
|
126
|
+
parentPathStat: policyDirectory.stat,
|
|
127
|
+
observation: policyDirectory.observation,
|
|
128
|
+
policyDirectory,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function policyParentCaptureDeopt(error) {
|
|
132
|
+
return error instanceof FsSafeError &&
|
|
133
|
+
(error.code === "path-mismatch" || error.code === "not-file");
|
|
134
|
+
}
|
|
135
|
+
function tryDescribePolicyPosixParent(parentFd, pathname) {
|
|
136
|
+
try {
|
|
137
|
+
return describePolicyPosixParent(parentFd, pathname);
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
if (!policyParentCaptureDeopt(error))
|
|
141
|
+
throw error;
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function assertPosixParentCurrent(parent) {
|
|
146
|
+
return parent.policyDirectory
|
|
147
|
+
? assertPolicyStagedDirectoryCurrent(parent.policyDirectory)
|
|
148
|
+
: assertStagedDirectoryCurrent(parent.directory);
|
|
149
|
+
}
|
|
150
|
+
async function capturePolicyAwarePosixParent(binding, params, rootFd, directoryFlags) {
|
|
151
|
+
const segments = relativeParentSegments(params.relativeParentPath);
|
|
152
|
+
const parentSpelling = segments.length
|
|
153
|
+
? path.join(params.rootPath, ...segments)
|
|
154
|
+
: params.rootPath;
|
|
155
|
+
const segmentRoute = createPathSegmentRoute(segments);
|
|
156
|
+
const initialTarget = prospectiveTargetPath(params.rootPath, segmentRoute, 0, params.basename);
|
|
157
|
+
let retainedTargetPath = params.mutationAdmission?.beginParentWalk?.();
|
|
158
|
+
if (retainedTargetPath && !sameAbsolutePath(retainedTargetPath, initialTarget)) {
|
|
159
|
+
retainedTargetPath = undefined;
|
|
160
|
+
}
|
|
161
|
+
// Preserve the one-open hot path when the complete parent still exists.
|
|
162
|
+
// Policy is attached only after the opened descriptor is associated with its
|
|
163
|
+
// current canonical pathname, so a contained redirect cannot retain the old
|
|
164
|
+
// preflight authorization.
|
|
165
|
+
let completeParentFd;
|
|
166
|
+
try {
|
|
167
|
+
completeParentFd = binding.openBeneath(rootFd, params.relativeParentPath, directoryFlags).fd;
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
if (error?.code !== "ENOENT" || !params.mkdir)
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
if (completeParentFd !== undefined) {
|
|
174
|
+
const parentFd = completeParentFd;
|
|
175
|
+
try {
|
|
176
|
+
let admitted = retainedTargetPath
|
|
177
|
+
? tryDescribePolicyPosixParent(parentFd, parentSpelling)
|
|
178
|
+
: undefined;
|
|
179
|
+
if (!admitted) {
|
|
180
|
+
retainedTargetPath = undefined;
|
|
181
|
+
admitted = await describePosixParent(parentFd, parentSpelling);
|
|
182
|
+
}
|
|
183
|
+
const targetPath = path.join(admitted.parentPath, params.basename);
|
|
184
|
+
await authorizePinnedMutation(params, {
|
|
185
|
+
targetPath,
|
|
186
|
+
mutationPath: targetPath,
|
|
187
|
+
phase: "parent",
|
|
188
|
+
});
|
|
189
|
+
assertPosixParentCurrent(admitted);
|
|
190
|
+
return admitted;
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
fsSync.closeSync(parentFd);
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// The canonical preflight spelling has no intentional symlinks in its
|
|
198
|
+
// existing prefix. Walk missing-parent cases one direct child at a time so
|
|
199
|
+
// every existing/opened object is authorized and every mkdir is authorized
|
|
200
|
+
// before dispatch. O_NOFOLLOW makes a concurrently introduced link fail
|
|
201
|
+
// before the next component can be created through it.
|
|
202
|
+
const secureDirectoryFlags = directoryFlags | (fsSync.constants.O_NOFOLLOW ?? 0);
|
|
203
|
+
let currentFd = rootFd;
|
|
204
|
+
let currentOwnedFd;
|
|
205
|
+
let currentPath = params.rootPath;
|
|
206
|
+
let current = retainedTargetPath
|
|
207
|
+
? tryDescribePolicyPosixParent(rootFd, currentPath)
|
|
208
|
+
: undefined;
|
|
209
|
+
if (!current) {
|
|
210
|
+
retainedTargetPath = undefined;
|
|
211
|
+
current = await describePosixParent(rootFd, currentPath);
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
await authorizePinnedMutation(params, {
|
|
215
|
+
targetPath: retainedTargetPath ?? initialTarget,
|
|
216
|
+
mutationPath: retainedTargetPath ?? initialTarget,
|
|
217
|
+
phase: "parent",
|
|
218
|
+
});
|
|
219
|
+
assertPosixParentCurrent(current);
|
|
220
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
221
|
+
const segment = segments[index];
|
|
222
|
+
const childPath = path.join(currentPath, segment);
|
|
223
|
+
let childFd;
|
|
224
|
+
let createdByMkdir = false;
|
|
225
|
+
let createReceipt;
|
|
226
|
+
try {
|
|
227
|
+
childFd = binding.openBeneath(currentFd, segment, secureDirectoryFlags).fd;
|
|
228
|
+
}
|
|
229
|
+
catch (openError) {
|
|
230
|
+
if (!isNotFoundPathError(openError)) {
|
|
231
|
+
throw normalizePolicyParentOpenError(openError, params);
|
|
232
|
+
}
|
|
233
|
+
const targetPath = retainedTargetPath ?? prospectiveTargetPath(currentPath, segmentRoute, index, params.basename);
|
|
234
|
+
const request = Object.freeze({
|
|
235
|
+
targetPath,
|
|
236
|
+
mutationPath: childPath,
|
|
237
|
+
phase: "parent-create",
|
|
238
|
+
});
|
|
239
|
+
createReceipt = params.mutationAdmission?.tryAuthorizeAtParent?.(request, current.observation);
|
|
240
|
+
if (!createReceipt) {
|
|
241
|
+
createReceipt = await authorizePinnedMutation(params, request);
|
|
242
|
+
assertPosixParentCurrent(current);
|
|
243
|
+
}
|
|
244
|
+
params.assertBeforeMutation?.();
|
|
245
|
+
// Do not carry pathname freshness across the live authority callback.
|
|
246
|
+
assertPosixParentCurrent(current);
|
|
247
|
+
createdByMkdir = mkdirPolicyChild(binding, currentFd, segment, 0o777);
|
|
248
|
+
try {
|
|
249
|
+
childFd = binding.openBeneath(currentFd, segment, secureDirectoryFlags).fd;
|
|
250
|
+
}
|
|
251
|
+
catch (createdOpenError) {
|
|
252
|
+
throw normalizePolicyParentOpenError(createdOpenError, params);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
let child;
|
|
256
|
+
try {
|
|
257
|
+
const acceleratedChild = retainedTargetPath
|
|
258
|
+
? tryDescribePolicyPosixParent(childFd, childPath)
|
|
259
|
+
: undefined;
|
|
260
|
+
if (acceleratedChild) {
|
|
261
|
+
child = acceleratedChild;
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
retainedTargetPath = undefined;
|
|
265
|
+
child = await describePosixParent(childFd, childPath);
|
|
266
|
+
}
|
|
267
|
+
if (!sameAbsolutePath(child.parentPath, childPath))
|
|
268
|
+
retainedTargetPath = undefined;
|
|
269
|
+
let childAuthorization;
|
|
270
|
+
if (createdByMkdir && createReceipt && params.mutationAdmission?.advanceCreatedDirectory) {
|
|
271
|
+
let evidence;
|
|
272
|
+
try {
|
|
273
|
+
const parent = current.policyDirectory
|
|
274
|
+
? refreshPolicyStagedDirectoryObservation(current.policyDirectory)
|
|
275
|
+
: checkedMutationDirectory(currentPath, current.directory.realPath, assertStagedDirectoryCurrent(current.directory));
|
|
276
|
+
evidence = Object.freeze({
|
|
277
|
+
admission: createReceipt,
|
|
278
|
+
parent,
|
|
279
|
+
child: child.observation,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
// Leave failed evidence to the full ordered admission below.
|
|
284
|
+
}
|
|
285
|
+
if (evidence) {
|
|
286
|
+
childAuthorization = params.mutationAdmission.advanceCreatedDirectory(evidence);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const targetPath = retainedTargetPath ?? prospectiveTargetPath(child.parentPath, segmentRoute, index + 1, params.basename);
|
|
290
|
+
const request = Object.freeze({
|
|
291
|
+
targetPath,
|
|
292
|
+
mutationPath: targetPath,
|
|
293
|
+
phase: "parent",
|
|
294
|
+
});
|
|
295
|
+
childAuthorization ??= params.mutationAdmission?.tryAuthorizeAtParent?.(request, child.observation);
|
|
296
|
+
if (!childAuthorization) {
|
|
297
|
+
await authorizePinnedMutation(params, request);
|
|
298
|
+
assertPosixParentCurrent(child);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
catch (error) {
|
|
302
|
+
fsSync.closeSync(childFd);
|
|
303
|
+
throw error;
|
|
304
|
+
}
|
|
305
|
+
const previousOwnedFd = currentOwnedFd;
|
|
306
|
+
currentOwnedFd = child.parentFd;
|
|
307
|
+
currentFd = child.parentFd;
|
|
308
|
+
currentPath = child.parentPath;
|
|
309
|
+
current = child;
|
|
310
|
+
if (previousOwnedFd !== undefined)
|
|
311
|
+
fsSync.closeSync(previousOwnedFd);
|
|
312
|
+
if (index === segments.length - 1) {
|
|
313
|
+
currentOwnedFd = undefined;
|
|
314
|
+
return child;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// Empty relative parents are handled by the complete-parent fast path.
|
|
318
|
+
throw new FsSafeError("path-mismatch", "native write parent admission did not complete");
|
|
319
|
+
}
|
|
320
|
+
finally {
|
|
321
|
+
if (currentOwnedFd !== undefined)
|
|
322
|
+
fsSync.closeSync(currentOwnedFd);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
63
325
|
export async function runPinnedWriteNative(binding, params) {
|
|
64
326
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
65
327
|
try {
|
|
@@ -68,7 +330,12 @@ export async function runPinnedWriteNative(binding, params) {
|
|
|
68
330
|
assertNativeStaging(binding);
|
|
69
331
|
}
|
|
70
332
|
const directoryFlags = fsSync.constants.O_RDONLY | (fsSync.constants.O_DIRECTORY ?? 0);
|
|
71
|
-
const
|
|
333
|
+
const rootAdmission = await openNativeRootAdmission(binding, {
|
|
334
|
+
rootPath: params.rootPath,
|
|
335
|
+
rootIdentity: params.rootIdentity,
|
|
336
|
+
operation: "native write",
|
|
337
|
+
});
|
|
338
|
+
const root = rootAdmission.root;
|
|
72
339
|
const posixRoot = __addDisposableResource(env_1, windows ? undefined : root, true);
|
|
73
340
|
let parentFd;
|
|
74
341
|
let windowsOwnsDirectories = false;
|
|
@@ -82,44 +349,37 @@ export async function runPinnedWriteNative(binding, params) {
|
|
|
82
349
|
},
|
|
83
350
|
}, false);
|
|
84
351
|
try {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
let
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
352
|
+
let parentPath;
|
|
353
|
+
let directory;
|
|
354
|
+
let parentPathStat;
|
|
355
|
+
let policyParentAdmitted = false;
|
|
356
|
+
if (!windows && params.mutationAdmission) {
|
|
357
|
+
const admitted = await capturePolicyAwarePosixParent(binding, params, root.fd, directoryFlags);
|
|
358
|
+
parentFd = admitted.parentFd;
|
|
359
|
+
parentPath = admitted.parentPath;
|
|
360
|
+
directory = admitted.directory;
|
|
361
|
+
parentPathStat = admitted.parentPathStat;
|
|
362
|
+
policyParentAdmitted = true;
|
|
95
363
|
}
|
|
96
364
|
else {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (!rootMatches) {
|
|
101
|
-
throw new FsSafeError("path-mismatch", "root path changed during native write");
|
|
102
|
-
}
|
|
103
|
-
if (params.mkdir) {
|
|
104
|
-
params.assertBeforeMutation?.();
|
|
105
|
-
binding.mkdirBeneath(root.fd, params.relativeParentPath, 0o777);
|
|
106
|
-
}
|
|
107
|
-
parentFd = binding.openBeneath(root.fd, params.relativeParentPath, directoryFlags).fd;
|
|
108
|
-
const parentPath = realpathSync.native(params.relativeParentPath
|
|
109
|
-
? path.join(params.rootPath, ...params.relativeParentPath.split("/"))
|
|
110
|
-
: params.rootPath);
|
|
111
|
-
const directory = windows ? undefined : describeStagedDirectory(parentFd, parentPath);
|
|
112
|
-
const parentPathStat = exactRoot
|
|
113
|
-
? await inspectDirectoryIdentity(parentPath, inspectFileIdentitySync(() => fsSync.fstatSync(parentFd, { bigint: true })))
|
|
114
|
-
: fsSync.lstatSync(parentPath);
|
|
115
|
-
if (windows && !exactRoot) {
|
|
116
|
-
const parentIdentity = binding.fstatIdentity(parentFd);
|
|
117
|
-
if (parentPathStat.isSymbolicLink() || !sameNativeIdentity(parentPathStat, parentIdentity)) {
|
|
118
|
-
throw new FsSafeError("path-mismatch", "native write parent changed during resolution");
|
|
365
|
+
if (params.mkdir) {
|
|
366
|
+
params.assertBeforeMutation?.();
|
|
367
|
+
binding.mkdirBeneath(root.fd, params.relativeParentPath, 0o777);
|
|
119
368
|
}
|
|
369
|
+
const admitted = await openNativeParentAdmission(binding, rootAdmission, params.relativeParentPath);
|
|
370
|
+
parentFd = admitted.fd;
|
|
371
|
+
parentPath = admitted.guard.realPath;
|
|
372
|
+
directory = admitted.stagedDirectory;
|
|
373
|
+
parentPathStat = admitted.guard.stat;
|
|
120
374
|
}
|
|
121
|
-
|
|
122
|
-
|
|
375
|
+
if (!policyParentAdmitted && params.mutationAdmission) {
|
|
376
|
+
const targetPath = path.join(parentPath, params.basename);
|
|
377
|
+
await authorizePinnedMutation(params, {
|
|
378
|
+
targetPath,
|
|
379
|
+
mutationPath: targetPath,
|
|
380
|
+
phase: "parent",
|
|
381
|
+
});
|
|
382
|
+
await inspectDirectoryIdentity(parentPath, inspectFileIdentitySync(() => fsSync.fstatSync(parentFd, { bigint: true })));
|
|
123
383
|
}
|
|
124
384
|
const verificationGuard = { dir: parentPath, realPath: parentPath, stat: parentPathStat };
|
|
125
385
|
if (params.overwrite === false) {
|
|
@@ -145,7 +405,16 @@ export async function runPinnedWriteNative(binding, params) {
|
|
|
145
405
|
finally {
|
|
146
406
|
if (windows && !windowsOwnsDirectories) {
|
|
147
407
|
if (parentFd !== undefined) {
|
|
148
|
-
|
|
408
|
+
// Match the Windows leaf cleanup contract: an admission or identity
|
|
409
|
+
// failure remains primary, while every owned directory is still given
|
|
410
|
+
// a close attempt. In particular, a parent close failure must not skip
|
|
411
|
+
// the root FileHandle close.
|
|
412
|
+
try {
|
|
413
|
+
fsSync.closeSync(parentFd);
|
|
414
|
+
}
|
|
415
|
+
catch {
|
|
416
|
+
// Best effort while propagating the operation failure.
|
|
417
|
+
}
|
|
149
418
|
}
|
|
150
419
|
await root.close().catch(() => undefined);
|
|
151
420
|
}
|
package/dist/output.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AAWA,MAAM,MAAM,wBAAwB,CAAC,CAAC,GAAG,IAAI,IAAI;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAClC,uEAAuE;IACvE,iBAAiB,CAAC,EAAE,mBAAmB,CAAC;IACxC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,uBAAuB,CAAC,CAAC,GAAG,IAAI,IAAI;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,CAAC,CAAC;CACX,CAAC;AAyDF,wBAAsB,2BAA2B,CAAC,CAAC,GAAG,IAAI,EACxD,OAAO,EAAE,wBAAwB,CAAC,CAAC,CAAC,GACnC,OAAO,CAAC,uBAAuB,CAAC,CAAC,CAAC,CAAC,CA2DrC"}
|
package/dist/output.js
CHANGED
|
@@ -7,6 +7,7 @@ import { fitFileNameToPortableComponent, sanitizeUntrustedFileName } from "./fil
|
|
|
7
7
|
import { isPathInside } from "./path.js";
|
|
8
8
|
import { root } from "./root.js";
|
|
9
9
|
import { tempFile } from "./temp-target.js";
|
|
10
|
+
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
10
11
|
const NON_PORTABLE_FILE_NAME_CHARACTERS = /[\u0000-\u001f\u007f-\u009f<>:"/\\|?*]/u;
|
|
11
12
|
function tempFileNameForTarget(targetPath, fallbackFileName) {
|
|
12
13
|
const fallback = sanitizeUntrustedFileName(fallbackFileName ?? "output.bin", "output.bin");
|
|
@@ -51,8 +52,12 @@ function assertFileTargetPath(targetPath) {
|
|
|
51
52
|
}
|
|
52
53
|
export async function writeExternalFileWithinRoot(options) {
|
|
53
54
|
const maxBytes = normalizeMaxBytes(options.maxBytes);
|
|
54
|
-
const
|
|
55
|
+
const rootDir = options.rootDir;
|
|
56
|
+
assertNoWindowsPathAlias(rootDir, "filesystem", "output root uses a Windows filesystem namespace alias");
|
|
55
57
|
const requestedTargetPath = options.path;
|
|
58
|
+
assertNoWindowsPathAlias(path.dirname(requestedTargetPath), "filesystem", "output target parent uses a Windows filesystem namespace alias");
|
|
59
|
+
const producerIsolation = options.producerIsolation;
|
|
60
|
+
const targetRoot = await root(rootDir);
|
|
56
61
|
if (requestedTargetPath.length === 0) {
|
|
57
62
|
throw new FsSafeError("invalid-path", "target path is required");
|
|
58
63
|
}
|
|
@@ -64,7 +69,9 @@ export async function writeExternalFileWithinRoot(options) {
|
|
|
64
69
|
});
|
|
65
70
|
assertFileTargetPath(rawTargetPath);
|
|
66
71
|
const targetPath = sanitizedTargetPath(rawTargetPath, options.fallbackFileName);
|
|
72
|
+
assertNoWindowsPathAlias(targetPath, "filesystem", "output target uses a Windows filesystem namespace alias");
|
|
67
73
|
const finalPath = await targetRoot.resolve(targetPath);
|
|
74
|
+
assertNoWindowsPathAlias(finalPath, "filesystem", "output target uses a Windows filesystem namespace alias");
|
|
68
75
|
if (options.staging === "sibling") {
|
|
69
76
|
const parentPath = path.dirname(targetPath);
|
|
70
77
|
if (parentPath !== ".") {
|
|
@@ -74,7 +81,7 @@ export async function writeExternalFileWithinRoot(options) {
|
|
|
74
81
|
const result = await writeExternalFileViaSibling({
|
|
75
82
|
finalPath: siblingFinalPath,
|
|
76
83
|
write: options.write,
|
|
77
|
-
producerIsolation
|
|
84
|
+
producerIsolation,
|
|
78
85
|
fallbackFileName: options.fallbackFileName,
|
|
79
86
|
maxBytes,
|
|
80
87
|
mode: options.mode,
|
|
@@ -99,7 +106,7 @@ export async function writeExternalFileWithinRoot(options) {
|
|
|
99
106
|
await staged.cleanup();
|
|
100
107
|
}
|
|
101
108
|
}
|
|
102
|
-
function
|
|
109
|
+
function buildSiblingTempName(targetPath, fallbackFileName) {
|
|
103
110
|
const prefix = `.fs-safe-output-${process.pid}-${randomUUID()}-`;
|
|
104
111
|
const suffix = ".part";
|
|
105
112
|
const safeTail = fitFileNameToPortableComponent({
|
|
@@ -107,12 +114,15 @@ function buildSiblingTempPath(targetPath, fallbackFileName) {
|
|
|
107
114
|
fileName: tempFileNameForTarget(targetPath, fallbackFileName),
|
|
108
115
|
suffix,
|
|
109
116
|
});
|
|
110
|
-
return
|
|
117
|
+
return `${prefix}${safeTail}${suffix}`;
|
|
111
118
|
}
|
|
112
119
|
async function writeExternalFileViaSibling(params) {
|
|
120
|
+
assertNoWindowsPathAlias(params.finalPath, "filesystem", "output target uses a Windows filesystem namespace alias");
|
|
113
121
|
const finalPath = path.resolve(params.finalPath);
|
|
122
|
+
assertNoWindowsPathAlias(finalPath, "filesystem", "output target uses a Windows filesystem namespace alias");
|
|
114
123
|
const { result } = await writeCallbackSibling({
|
|
115
|
-
|
|
124
|
+
tempDir: path.dirname(finalPath),
|
|
125
|
+
tempName: buildSiblingTempName(finalPath, params.fallbackFileName),
|
|
116
126
|
write: params.write,
|
|
117
127
|
producerIsolation: params.producerIsolation,
|
|
118
128
|
resolveFinalPath: () => finalPath,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"overwrite-file-handle.d.ts","sourceRoot":"","sources":["../src/overwrite-file-handle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAMnD,MAAM,MAAM,0BAA0B,GAAG;IACvC,sFAAsF;IACtF,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;CAC1B,CAAC;AAEF,4FAA4F;AAC5F,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,UAAU,EAChB,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"overwrite-file-handle.d.ts","sourceRoot":"","sources":["../src/overwrite-file-handle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAMnD,MAAM,MAAM,0BAA0B,GAAG;IACvC,sFAAsF;IACtF,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;CAC1B,CAAC;AAEF,4FAA4F;AAC5F,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,UAAU,EAChB,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,IAAI,CAAC,CA2Cf"}
|
|
@@ -4,6 +4,10 @@ import { readFileWindowFully } from "./positional-read.js";
|
|
|
4
4
|
import { writeAllToFile } from "./write-file-handle.js";
|
|
5
5
|
/** Overwrite a borrowed, non-append read/write handle with best-effort failure rollback. */
|
|
6
6
|
export async function overwriteFileHandle(handle, data, options = {}) {
|
|
7
|
+
// Snapshot caller-owned admission before the first await. Preserve the
|
|
8
|
+
// former method-call receiver while preventing a later getter mutation from
|
|
9
|
+
// replacing authority after rollback preparation.
|
|
10
|
+
const beforeWrite = options.beforeWrite;
|
|
7
11
|
const payload = data.subarray(0, data.byteLength);
|
|
8
12
|
const payloadBytes = payload.byteLength;
|
|
9
13
|
const stat = await handle.stat();
|
|
@@ -19,7 +23,7 @@ export async function overwriteFileHandle(handle, data, options = {}) {
|
|
|
19
23
|
if (await readFileWindowFully(handle, originalPrefix, 0) !== prefixBytes) {
|
|
20
24
|
throw new FsSafeError("read-failed", "file ended before overwrite rollback preparation completed");
|
|
21
25
|
}
|
|
22
|
-
assertSynchronousCallbackResult(
|
|
26
|
+
assertSynchronousCallbackResult(beforeWrite === undefined ? undefined : Reflect.apply(beforeWrite, options, []), "beforeWrite");
|
|
23
27
|
let prefixStarted = false;
|
|
24
28
|
try {
|
|
25
29
|
// Leave existing bytes intact until any additional storage has been written.
|
package/dist/owner-dacl.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"owner-dacl.d.ts","sourceRoot":"","sources":["../src/owner-dacl.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"owner-dacl.d.ts","sourceRoot":"","sources":["../src/owner-dacl.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,EAAE,OAAO,CAAC;IACvB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,YAAY,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC;IAC1B,KAAK,EAAE,eAAe,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC1B;IACE,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,IAAI,EAAE,yBAAyB,EAAE,CAAC;CACnC,GACD;IACE,MAAM,EAAE,sBAAsB,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;CAC3B,CAAC;AAEN,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,kBAAkB,CAmCvE"}
|
package/dist/owner-dacl.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { FsSafeError } from "./errors.js";
|
|
2
2
|
import { getNativeBinding } from "./native.js";
|
|
3
|
+
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
3
4
|
export function readOwnerAndDacl(targetPath) {
|
|
4
5
|
if (process.platform !== "win32") {
|
|
5
6
|
return { status: "unsupported-platform", platform: process.platform };
|
|
6
7
|
}
|
|
8
|
+
assertNoWindowsPathAlias(targetPath, "filesystem", "owner and DACL path uses a Windows filesystem namespace alias");
|
|
7
9
|
const native = getNativeBinding();
|
|
8
10
|
if (!native) {
|
|
9
11
|
throw new FsSafeError("helper-unavailable", "Windows owner and DACL facts require the matching optional native platform package; " +
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-policy.d.ts","sourceRoot":"","sources":["../src/path-policy.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"path-policy.d.ts","sourceRoot":"","sources":["../src/path-policy.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,gBAAgB,CAAC;AAKxB,MAAM,MAAM,eAAe,GAAG,mBAAmB,CAAC;AAElD,eAAO,MAAM,mBAAmB;;;;;;;;;CAA2B,CAAC;AAE5D,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,eAAe,CAAC;CAC1B,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBhB;AAED,wBAAsB,2BAA2B,CAAC,MAAM,EAAE;IACxD,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBhB"}
|
package/dist/path-policy.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import { ROOT_PATH_ALIAS_POLICIES, resolveRootPath, } from "./root-path.js";
|
|
3
3
|
import { isNotFoundPathError } from "./path.js";
|
|
4
4
|
import { shortPath } from "./error-detail.js";
|
|
5
|
+
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
5
6
|
export const PATH_ALIAS_POLICIES = ROOT_PATH_ALIAS_POLICIES;
|
|
6
7
|
export async function assertNoPathAliasEscape(params) {
|
|
7
8
|
const resolved = await resolveRootPath({
|
|
@@ -25,9 +26,13 @@ export async function assertNoHardlinkedFinalPath(params) {
|
|
|
25
26
|
if (params.allowFinalHardlinkForUnlink) {
|
|
26
27
|
return;
|
|
27
28
|
}
|
|
29
|
+
const filePath = params.filePath;
|
|
30
|
+
const root = params.root;
|
|
31
|
+
const boundaryLabel = params.boundaryLabel;
|
|
32
|
+
assertNoWindowsPathAlias(filePath);
|
|
28
33
|
let stat;
|
|
29
34
|
try {
|
|
30
|
-
stat = fs.statSync(
|
|
35
|
+
stat = fs.statSync(filePath);
|
|
31
36
|
}
|
|
32
37
|
catch (err) {
|
|
33
38
|
if (isNotFoundPathError(err)) {
|
|
@@ -39,6 +44,6 @@ export async function assertNoHardlinkedFinalPath(params) {
|
|
|
39
44
|
return;
|
|
40
45
|
}
|
|
41
46
|
if (stat.nlink > 1) {
|
|
42
|
-
throw new Error(`Hardlinked path is not allowed under ${
|
|
47
|
+
throw new Error(`Hardlinked path is not allowed under ${boundaryLabel} (${shortPath(root)}): ${shortPath(filePath)}`);
|
|
43
48
|
}
|
|
44
49
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type ResolvedPathPrefix = {
|
|
2
|
+
readonly absolutePath: string;
|
|
3
|
+
readonly existingPath: string;
|
|
4
|
+
readonly unresolvedSegments: readonly string[];
|
|
5
|
+
};
|
|
6
|
+
export declare function resolvePathPrefixSync(input: string): ResolvedPathPrefix;
|
|
7
|
+
//# sourceMappingURL=path-prefix.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-prefix.d.ts","sourceRoot":"","sources":["../src/path-prefix.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,kBAAkB,EAAE,SAAS,MAAM,EAAE,CAAC;CAChD,CAAC;AAkBF,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,kBAAkB,CAsDvE"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { formatErrorDetail } from "./error-detail.js";
|
|
4
|
+
import { assertNoNulPathInput, hasNodeErrorCode } from "./path.js";
|
|
5
|
+
import { realpathSync } from "./realpath.js";
|
|
6
|
+
import { absolutePathWithRawSegments } from "./root-path-existing.js";
|
|
7
|
+
import { pathForWindowsFilesystem } from "./windows-path-alias.js";
|
|
8
|
+
function rawRoot(absolutePath) {
|
|
9
|
+
if (path.sep === "\\" && /^\\\\[?.]\\UNC\\/i.test(absolutePath)) {
|
|
10
|
+
// Node parses only the namespace prefix as the root of an extended UNC path.
|
|
11
|
+
const shareRoot = path.parse(`\\\\${absolutePath.slice(8)}`).root;
|
|
12
|
+
return absolutePath.slice(0, shareRoot.length + 6);
|
|
13
|
+
}
|
|
14
|
+
return path.parse(absolutePath).root;
|
|
15
|
+
}
|
|
16
|
+
function resolutionError(code, absolutePath) {
|
|
17
|
+
const detail = code === "ELOOP" ? "too many symbolic links" : "not a directory";
|
|
18
|
+
return Object.assign(new Error(`${code}: ${detail}, resolvePathPrefix ${formatErrorDetail(absolutePath)}`), {
|
|
19
|
+
code, syscall: "resolvePathPrefix", path: absolutePath,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
export function resolvePathPrefixSync(input) {
|
|
23
|
+
assertNoNulPathInput(input);
|
|
24
|
+
const absolutePath = absolutePathWithRawSegments(input);
|
|
25
|
+
let resolved = rawRoot(absolutePath);
|
|
26
|
+
const remaining = absolutePath.slice(resolved.length).split(path.sep);
|
|
27
|
+
const visitedStates = new Set();
|
|
28
|
+
let symlinkHops = 0;
|
|
29
|
+
while (remaining.length > 0) {
|
|
30
|
+
const segment = remaining.shift();
|
|
31
|
+
if (segment === "")
|
|
32
|
+
continue;
|
|
33
|
+
if (segment === "." || segment === "..") {
|
|
34
|
+
const atRoot = resolved === rawRoot(resolved);
|
|
35
|
+
const lookup = segment === ".." && atRoot ? "." : segment;
|
|
36
|
+
// lstat(dir) does not test search permission, and realpath may erase dots.
|
|
37
|
+
const operationBase = pathForWindowsFilesystem(resolved);
|
|
38
|
+
fs.lstatSync(`${operationBase}${operationBase.endsWith(path.sep) ? "" : path.sep}${lookup}`, { bigint: true });
|
|
39
|
+
if (segment === ".." && !atRoot)
|
|
40
|
+
resolved = path.dirname(resolved);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const candidate = path.join(resolved, segment);
|
|
44
|
+
let stat;
|
|
45
|
+
try {
|
|
46
|
+
stat = fs.lstatSync(candidate, { bigint: true });
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (!hasNodeErrorCode(error, "ENOENT"))
|
|
50
|
+
throw error;
|
|
51
|
+
return {
|
|
52
|
+
absolutePath,
|
|
53
|
+
existingPath: realpathSync.native(pathForWindowsFilesystem(resolved)),
|
|
54
|
+
unresolvedSegments: [segment, ...remaining],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (!stat.isSymbolicLink()) {
|
|
58
|
+
if (!stat.isDirectory() && remaining.length > 0)
|
|
59
|
+
throw resolutionError("ENOTDIR", absolutePath);
|
|
60
|
+
resolved = candidate;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const state = JSON.stringify([String(stat.dev), String(stat.ino), candidate, remaining]);
|
|
64
|
+
if (symlinkHops >= 64 || visitedStates.has(state))
|
|
65
|
+
throw resolutionError("ELOOP", absolutePath);
|
|
66
|
+
visitedStates.add(state);
|
|
67
|
+
symlinkHops++;
|
|
68
|
+
const target = fs.readlinkSync(candidate);
|
|
69
|
+
const rawTarget = path.sep === "\\" ? target.replaceAll("/", "\\") : target;
|
|
70
|
+
if (path.isAbsolute(rawTarget)) {
|
|
71
|
+
const targetRoot = rawRoot(rawTarget);
|
|
72
|
+
// A rooted Windows link target uses the link's drive/share, not cwd's.
|
|
73
|
+
resolved = path.sep === "\\" && targetRoot === "\\" ? rawRoot(resolved) : targetRoot;
|
|
74
|
+
remaining.unshift(...rawTarget.slice(targetRoot.length).split(path.sep));
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
// A target's parent traversal applies after resolving its preceding links.
|
|
78
|
+
remaining.unshift(...rawTarget.split(path.sep));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return { absolutePath, existingPath: realpathSync.native(pathForWindowsFilesystem(resolved)), unresolvedSegments: [] };
|
|
82
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-scope-lexical.d.ts","sourceRoot":"","sources":["../src/path-scope-lexical.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"path-scope-lexical.d.ts","sourceRoot":"","sources":["../src/path-scope-lexical.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,2BAA2B,GAAG;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAUF,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,2BAA2B,EACnC,IAAI,EAAE,MAAM,GACX;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CA4B3D"}
|