@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
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import fs, {} from "node:fs";
|
|
2
|
+
export function captureSecureTempRepairAdapter(supplied, hasLegacyFsHooks) {
|
|
3
|
+
if (supplied === undefined && hasLegacyFsHooks)
|
|
4
|
+
return undefined;
|
|
5
|
+
if (supplied !== undefined && (supplied === null || (typeof supplied !== "object" && typeof supplied !== "function")))
|
|
6
|
+
return undefined;
|
|
7
|
+
const source = supplied === undefined ? fs : supplied;
|
|
8
|
+
const { lstatSync, fstatSync, openSync, fchmodSync, closeSync, constants } = source;
|
|
9
|
+
const { O_RDONLY, O_DIRECTORY, O_NOFOLLOW, O_NONBLOCK } = constants ?? {};
|
|
10
|
+
if ([lstatSync, fstatSync, openSync, fchmodSync, closeSync].some((fn) => typeof fn !== "function") ||
|
|
11
|
+
O_RDONLY !== 0 || [O_DIRECTORY, O_NOFOLLOW, O_NONBLOCK].some((flag) => !Number.isSafeInteger(flag) || flag <= 0 || flag > 0x7fff_ffff) ||
|
|
12
|
+
(O_DIRECTORY & O_NOFOLLOW) !== 0 || (O_DIRECTORY & O_NONBLOCK) !== 0 || (O_NOFOLLOW & O_NONBLOCK) !== 0) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
return { lstatSync, fstatSync, openSync, fchmodSync, closeSync, flags: O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_NONBLOCK };
|
|
16
|
+
}
|
|
17
|
+
export function secureTempDirectoryReceipt(stat, uid) {
|
|
18
|
+
const { dev, ino, uid: owner, mode } = stat;
|
|
19
|
+
if (typeof dev !== "bigint" || dev < 0n || typeof ino !== "bigint" || ino <= 0n ||
|
|
20
|
+
typeof owner !== "bigint" || owner !== BigInt(uid) || typeof mode !== "bigint" ||
|
|
21
|
+
mode < 0n || mode > 0xffffffffn || (mode & 61440n) !== 16384n ||
|
|
22
|
+
stat.isDirectory() !== true || stat.isSymbolicLink() !== false) {
|
|
23
|
+
throw new Error("Secure temp directory identity, owner, or type could not be verified.");
|
|
24
|
+
}
|
|
25
|
+
return { dev, ino, uid: owner, mode };
|
|
26
|
+
}
|
|
27
|
+
function assertSameDirectory(stat, expected, uid) {
|
|
28
|
+
const observed = secureTempDirectoryReceipt(stat, uid);
|
|
29
|
+
if (observed.dev !== expected.dev || observed.ino !== expected.ino || observed.uid !== expected.uid) {
|
|
30
|
+
throw new Error("Secure temp directory changed during repair.");
|
|
31
|
+
}
|
|
32
|
+
return observed;
|
|
33
|
+
}
|
|
34
|
+
function mayHaveConcurrentRepair(error) {
|
|
35
|
+
if (typeof error !== "object" || error === null || !("code" in error))
|
|
36
|
+
return false;
|
|
37
|
+
const code = error.code;
|
|
38
|
+
return code === "EPERM" || code === "EACCES" || code === "ENOENT";
|
|
39
|
+
}
|
|
40
|
+
/** No pathname chmod, search-only descriptors, procfs, or reopening fallback. */
|
|
41
|
+
export function repairSecureTempDirectory(candidate, expected, uid, adapter, access, repaired, finalize = false) {
|
|
42
|
+
assertSameDirectory(adapter.lstatSync(candidate, { bigint: true }), expected, uid);
|
|
43
|
+
const fd = adapter.openSync(candidate, adapter.flags);
|
|
44
|
+
if (!Number.isSafeInteger(fd) || fd < 0)
|
|
45
|
+
throw new Error("Secure temp directory descriptor is invalid.");
|
|
46
|
+
const errors = [];
|
|
47
|
+
try {
|
|
48
|
+
const inspect = () => {
|
|
49
|
+
const opened = assertSameDirectory(adapter.fstatSync(fd, { bigint: true }), expected, uid);
|
|
50
|
+
const named = assertSameDirectory(adapter.lstatSync(candidate, { bigint: true }), expected, uid);
|
|
51
|
+
return { opened, named };
|
|
52
|
+
};
|
|
53
|
+
const verify = (requireExactMode) => {
|
|
54
|
+
const check = () => {
|
|
55
|
+
const { opened, named } = inspect();
|
|
56
|
+
for (const stat of [opened, named]) {
|
|
57
|
+
if ((stat.mode & 18n) !== 0n || (requireExactMode && (stat.mode & 4095n) !== 448n)) {
|
|
58
|
+
throw new Error("Secure temp directory permissions remain unsafe.");
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
check();
|
|
63
|
+
access();
|
|
64
|
+
check();
|
|
65
|
+
};
|
|
66
|
+
const { opened } = inspect();
|
|
67
|
+
const needsChmod = finalize ? (opened.mode & 4095n) !== 448n :
|
|
68
|
+
(opened.mode & 18n) !== 0n || (opened.mode & 448n) !== 448n;
|
|
69
|
+
let chmodError;
|
|
70
|
+
let chmodFailed = false;
|
|
71
|
+
if (needsChmod) {
|
|
72
|
+
try {
|
|
73
|
+
adapter.fchmodSync(fd, 0o700);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
if (!mayHaveConcurrentRepair(error))
|
|
77
|
+
throw error;
|
|
78
|
+
chmodFailed = true;
|
|
79
|
+
chmodError = error;
|
|
80
|
+
}
|
|
81
|
+
if (!chmodFailed)
|
|
82
|
+
repaired();
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
verify(finalize || (needsChmod && !chmodFailed));
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
if (chmodFailed)
|
|
89
|
+
throw new AggregateError([chmodError, error], "Secure temp directory chmod and verification failed.");
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
errors.push(error);
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
adapter.closeSync(fd);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
errors.push(error);
|
|
101
|
+
}
|
|
102
|
+
if (errors.length > 1)
|
|
103
|
+
throw new AggregateError(errors, "Secure temp directory repair and close failed.");
|
|
104
|
+
if (errors.length === 1)
|
|
105
|
+
throw errors[0];
|
|
106
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export declare function
|
|
1
|
+
export declare function resolveCallbackTempPath(workspaceDir: string, component: string): string;
|
|
2
2
|
export declare function writeCallbackSibling<T>(params: {
|
|
3
|
-
|
|
3
|
+
tempDir: string;
|
|
4
|
+
tempName: string;
|
|
4
5
|
write: (tempPath: string) => Promise<T>;
|
|
5
6
|
producerIsolation?: "private-directory";
|
|
6
7
|
resolveFinalPath: (result: T) => string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sibling-staged-file.d.ts","sourceRoot":"","sources":["../src/sibling-staged-file.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sibling-staged-file.d.ts","sourceRoot":"","sources":["../src/sibling-staged-file.ts"],"names":[],"mappings":"AAsCA,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAoBvF;AAsHD,wBAAsB,oBAAoB,CAAC,CAAC,EAAE,MAAM,EAAE;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACxC,iBAAiB,CAAC,EAAE,mBAAmB,CAAC;IACxC,gBAAgB,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;CACxB,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,CAAC,CAAA;CAAE,CAAC,CAsK3C"}
|
|
@@ -6,12 +6,41 @@ import { assertAsyncDirectoryGuard, assertDirectoryIdentitySync, createAsyncDire
|
|
|
6
6
|
import { syncDirectoryBestEffort } from "./directory-durability.js";
|
|
7
7
|
import { isWindowsReservedDeviceName } from "./device-path.js";
|
|
8
8
|
import { FsSafeError } from "./errors.js";
|
|
9
|
+
import { getNativeBinding } from "./native.js";
|
|
10
|
+
import { handoffPrivateProducerFile, } from "./private-producer-handoff.js";
|
|
9
11
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
10
|
-
import {
|
|
12
|
+
import { rootFromDirectoryGuard } from "./root-impl.js";
|
|
11
13
|
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
12
14
|
import { registerTempPathForExit } from "./temp-cleanup.js";
|
|
13
15
|
import { createOwnedTempFile } from "./temp-target.js";
|
|
14
16
|
import { serializePathWrite } from "./write-queue.js";
|
|
17
|
+
import { assertNoWindowsPathAlias, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
18
|
+
const INVALID_CALLBACK_COMPONENT_CHARACTERS = /[\u0000-\u001f\u007f-\u009f<>:"/\\|?*]/u;
|
|
19
|
+
function resolveCallbackTempDirectory(workspaceDir) {
|
|
20
|
+
assertNoWindowsPathAlias(workspaceDir, "filesystem", "sibling temp parent uses a Windows filesystem namespace alias");
|
|
21
|
+
const dir = resolvePathPreservingWindowsRoot(workspaceDir);
|
|
22
|
+
assertNoWindowsPathAlias(dir, "filesystem", "sibling temp parent uses a Windows filesystem namespace alias");
|
|
23
|
+
return dir;
|
|
24
|
+
}
|
|
25
|
+
export function resolveCallbackTempPath(workspaceDir, component) {
|
|
26
|
+
const dir = resolveCallbackTempDirectory(workspaceDir);
|
|
27
|
+
if (typeof component !== "string" ||
|
|
28
|
+
component === "" ||
|
|
29
|
+
component === "." ||
|
|
30
|
+
component === ".." ||
|
|
31
|
+
INVALID_CALLBACK_COMPONENT_CHARACTERS.test(component) ||
|
|
32
|
+
component.endsWith(".") ||
|
|
33
|
+
component.endsWith(" ") ||
|
|
34
|
+
isWindowsReservedDeviceName(component)) {
|
|
35
|
+
throw new FsSafeError("invalid-path", "callback temp name must be one path component");
|
|
36
|
+
}
|
|
37
|
+
const joined = path.join(dir, component);
|
|
38
|
+
assertNoWindowsPathAlias(joined, "filesystem", "sibling temp path uses a Windows filesystem namespace alias");
|
|
39
|
+
if (path.dirname(joined) !== dir) {
|
|
40
|
+
throw new FsSafeError("invalid-path", "callback temp path must be a direct workspace child");
|
|
41
|
+
}
|
|
42
|
+
return joined;
|
|
43
|
+
}
|
|
15
44
|
function assertRegularFile(stat) {
|
|
16
45
|
if (stat.isSymbolicLink()) {
|
|
17
46
|
throw new FsSafeError("symlink", "symlink sibling temp not allowed");
|
|
@@ -30,48 +59,102 @@ async function inspectStage(inspect, expected) {
|
|
|
30
59
|
return stat;
|
|
31
60
|
}, expected);
|
|
32
61
|
}
|
|
33
|
-
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
|
|
62
|
+
function aggregateErrors(errors, message) {
|
|
63
|
+
if (errors.length === 1)
|
|
64
|
+
return errors[0];
|
|
65
|
+
return new AggregateError(errors, message);
|
|
37
66
|
}
|
|
38
67
|
// Own the workspace before the producer can leave partial output. The finished
|
|
39
68
|
// file still enters the ordinary sibling admission and publication lifecycle.
|
|
40
69
|
async function writeIsolatedProducer(params) {
|
|
41
|
-
const
|
|
70
|
+
const tempPath = params.tempPath;
|
|
71
|
+
const write = params.write;
|
|
72
|
+
const writeReceiver = params.writeReceiver;
|
|
73
|
+
const parentGuard = params.parentGuard;
|
|
74
|
+
const assertParent = params.assertParent;
|
|
75
|
+
const syncTempFile = params.syncTempFile;
|
|
76
|
+
assertParent();
|
|
77
|
+
const targetRoot = rootFromDirectoryGuard(parentGuard);
|
|
78
|
+
// Select the handoff before invoking the producer. Missing required native
|
|
79
|
+
// support must not leave producer output behind as a discovery side effect.
|
|
80
|
+
const native = getNativeBinding();
|
|
81
|
+
const cleanupErrors = [];
|
|
42
82
|
const { target, identity } = await createOwnedTempFile({
|
|
43
83
|
rootDir: targetRoot.rootReal,
|
|
44
84
|
prefix: "fs-safe-output",
|
|
45
|
-
fileName: path.basename(
|
|
85
|
+
fileName: path.basename(tempPath),
|
|
86
|
+
onCleanupError: (error) => cleanupErrors.push(error),
|
|
46
87
|
});
|
|
47
|
-
|
|
48
|
-
|
|
88
|
+
let cleanupStarted = false;
|
|
89
|
+
const cleanupWorkspace = async () => {
|
|
90
|
+
if (cleanupStarted)
|
|
91
|
+
return;
|
|
92
|
+
cleanupStarted = true;
|
|
93
|
+
try {
|
|
94
|
+
await target.cleanup();
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
cleanupErrors.push(error);
|
|
98
|
+
}
|
|
99
|
+
if (cleanupErrors.length > 0) {
|
|
100
|
+
throw aggregateErrors(cleanupErrors, "isolated producer workspace cleanup failed");
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const assertWorkspace = () => {
|
|
49
104
|
assertDirectoryIdentitySync(target.dir, { ...identity, realPath: target.dir });
|
|
50
105
|
};
|
|
106
|
+
const assertCurrent = () => {
|
|
107
|
+
assertParent();
|
|
108
|
+
assertWorkspace();
|
|
109
|
+
};
|
|
51
110
|
try {
|
|
111
|
+
const producerPath = resolveCallbackTempPath(target.dir, path.basename(target.path));
|
|
52
112
|
assertCurrent();
|
|
53
|
-
const result = await
|
|
113
|
+
const result = await Reflect.apply(write, writeReceiver, [producerPath]);
|
|
54
114
|
assertCurrent();
|
|
55
|
-
|
|
56
|
-
assertBeforeMutation: assertCurrent
|
|
115
|
+
if (native) {
|
|
116
|
+
await targetRoot.move(path.relative(targetRoot.rootReal, producerPath), path.basename(tempPath), { assertBeforeMutation: assertCurrent });
|
|
117
|
+
return { cleanupWorkspace, result };
|
|
118
|
+
}
|
|
119
|
+
const handoff = await handoffPrivateProducerFile({
|
|
120
|
+
sourcePath: producerPath,
|
|
121
|
+
targetPath: tempPath,
|
|
122
|
+
assertSourceParent: assertWorkspace,
|
|
123
|
+
assertTargetParent: assertParent,
|
|
124
|
+
readWrite: syncTempFile,
|
|
57
125
|
});
|
|
58
|
-
return result;
|
|
126
|
+
return { cleanupWorkspace, handoff, result };
|
|
59
127
|
}
|
|
60
|
-
|
|
61
|
-
|
|
128
|
+
catch (error) {
|
|
129
|
+
try {
|
|
130
|
+
await cleanupWorkspace();
|
|
131
|
+
}
|
|
132
|
+
catch (cleanupError) {
|
|
133
|
+
throw new AggregateError([error, cleanupError], "isolated producer operation and workspace cleanup failed");
|
|
134
|
+
}
|
|
135
|
+
throw error;
|
|
62
136
|
}
|
|
63
137
|
}
|
|
64
138
|
// Callback paths are not owned until all three admission observations agree.
|
|
65
139
|
// Keep one descriptor and one exact identity through mode, sync, rename and cleanup.
|
|
66
140
|
// Read/write access is needed only when the caller requests file synchronization.
|
|
67
141
|
export async function writeCallbackSibling(params) {
|
|
68
|
-
|
|
69
|
-
const
|
|
142
|
+
const parent = resolveCallbackTempDirectory(params.tempDir);
|
|
143
|
+
const tempPath = resolveCallbackTempPath(parent, params.tempName);
|
|
144
|
+
const write = params.write;
|
|
145
|
+
const producerIsolation = params.producerIsolation;
|
|
146
|
+
const resolveFinalPath = params.resolveFinalPath;
|
|
147
|
+
const mode = params.mode;
|
|
148
|
+
const ignoreModeError = params.ignoreModeError;
|
|
149
|
+
const maxBytes = params.maxBytes;
|
|
150
|
+
const syncTempFile = params.syncTempFile;
|
|
151
|
+
const syncParentDir = params.syncParentDir;
|
|
70
152
|
const guard = await createAsyncDirectoryGuard(parent, { bigint: true });
|
|
71
153
|
const assertParent = () => assertAsyncDirectoryGuard(guard);
|
|
72
154
|
let handle;
|
|
73
155
|
let identity;
|
|
74
156
|
let unregister;
|
|
157
|
+
let cleanupWorkspace;
|
|
75
158
|
let renamed = false;
|
|
76
159
|
let failure;
|
|
77
160
|
const inspectPath = (pathname, expected) => inspectStage(() => fsSync.lstatSync(pathname, { bigint: true }), expected);
|
|
@@ -79,70 +162,98 @@ export async function writeCallbackSibling(params) {
|
|
|
79
162
|
await assertParent();
|
|
80
163
|
const opened = await inspectStage(() => fsSync.fstatSync(handle.fd, { bigint: true }), identity);
|
|
81
164
|
const current = await inspectPath(pathname, opened);
|
|
82
|
-
if (
|
|
83
|
-
(opened.size >
|
|
84
|
-
throw new FsSafeError("too-large", `sibling temp exceeds maxBytes (${
|
|
165
|
+
if (maxBytes !== undefined &&
|
|
166
|
+
(opened.size > maxBytes || current.size > maxBytes)) {
|
|
167
|
+
throw new FsSafeError("too-large", `sibling temp exceeds maxBytes (${maxBytes})`);
|
|
85
168
|
}
|
|
86
169
|
};
|
|
87
170
|
try {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
171
|
+
let result;
|
|
172
|
+
if (producerIsolation === "private-directory") {
|
|
173
|
+
const isolated = await writeIsolatedProducer({
|
|
174
|
+
tempPath: tempPath,
|
|
175
|
+
write,
|
|
176
|
+
writeReceiver: params,
|
|
177
|
+
parentGuard: guard,
|
|
178
|
+
syncTempFile: syncTempFile,
|
|
92
179
|
assertParent: () => assertDirectoryIdentitySync(parent, {
|
|
93
180
|
dev: guard.stat.dev,
|
|
94
181
|
ino: guard.stat.ino,
|
|
95
182
|
realPath: guard.realPath,
|
|
96
183
|
}),
|
|
97
|
-
})
|
|
98
|
-
|
|
184
|
+
});
|
|
185
|
+
result = isolated.result;
|
|
186
|
+
cleanupWorkspace = isolated.cleanupWorkspace;
|
|
187
|
+
if (isolated.handoff) {
|
|
188
|
+
handle = isolated.handoff.handle;
|
|
189
|
+
identity = isolated.handoff.identity;
|
|
190
|
+
unregister = isolated.handoff.unregister;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
result = await Reflect.apply(write, params, [tempPath]);
|
|
195
|
+
}
|
|
99
196
|
await assertParent();
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const access = params.syncTempFile ? fsSync.constants.O_RDWR : fsSync.constants.O_RDONLY;
|
|
104
|
-
handle = await fs.open(params.tempPath, access | resolveReadOpenFlags());
|
|
197
|
+
if (handle) {
|
|
198
|
+
const opened = await inspectStage(() => fsSync.fstatSync(handle.fd, { bigint: true }), identity);
|
|
199
|
+
await inspectPath(tempPath, opened);
|
|
105
200
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
201
|
+
else {
|
|
202
|
+
const before = await inspectPath(tempPath);
|
|
203
|
+
try {
|
|
204
|
+
// No create/truncate flags; O_NONBLOCK also bounds a FIFO swap during open.
|
|
205
|
+
const access = syncTempFile ? fsSync.constants.O_RDWR : fsSync.constants.O_RDONLY;
|
|
206
|
+
handle = await fs.open(tempPath, access | resolveReadOpenFlags());
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
if (error?.code === "ELOOP") {
|
|
210
|
+
throw new FsSafeError("symlink", "symlink sibling temp not allowed", { cause: error });
|
|
211
|
+
}
|
|
212
|
+
throw error;
|
|
109
213
|
}
|
|
110
|
-
|
|
214
|
+
const opened = await inspectStage(() => fsSync.fstatSync(handle.fd, { bigint: true }), before);
|
|
215
|
+
await inspectPath(tempPath, opened);
|
|
216
|
+
identity = opened;
|
|
217
|
+
unregister = registerTempPathForExit(tempPath, { identity, singleLinkFile: true });
|
|
111
218
|
}
|
|
112
|
-
const opened = await inspectStage(() => fsSync.fstatSync(handle.fd, { bigint: true }), before);
|
|
113
|
-
await inspectPath(params.tempPath, opened);
|
|
114
219
|
await assertParent();
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
220
|
+
if (cleanupWorkspace) {
|
|
221
|
+
const cleanup = cleanupWorkspace;
|
|
222
|
+
cleanupWorkspace = undefined;
|
|
223
|
+
await cleanup();
|
|
224
|
+
}
|
|
225
|
+
const rawFilePath = Reflect.apply(resolveFinalPath, params, [result]);
|
|
226
|
+
assertNoWindowsPathAlias(rawFilePath, "filesystem", "final path uses a Windows filesystem namespace alias");
|
|
227
|
+
const filePath = path.resolve(rawFilePath);
|
|
228
|
+
assertNoWindowsPathAlias(filePath, "filesystem", "final path uses a Windows filesystem namespace alias");
|
|
118
229
|
if (path.dirname(filePath) !== parent) {
|
|
119
230
|
throw new Error("Final path must be in the sibling temp directory.");
|
|
120
231
|
}
|
|
121
|
-
if (filePath ===
|
|
232
|
+
if (filePath === tempPath) {
|
|
122
233
|
throw new FsSafeError("invalid-path", "final path must differ from the sibling temp path");
|
|
123
234
|
}
|
|
124
235
|
await serializePathWrite(filePath, async () => {
|
|
125
|
-
await assertCurrent(
|
|
126
|
-
if (
|
|
236
|
+
await assertCurrent(tempPath);
|
|
237
|
+
if (mode !== undefined) {
|
|
127
238
|
try {
|
|
128
|
-
await handle.chmod(
|
|
239
|
+
await handle.chmod(mode);
|
|
129
240
|
}
|
|
130
241
|
catch (error) {
|
|
131
|
-
if (!
|
|
242
|
+
if (!ignoreModeError)
|
|
132
243
|
throw error;
|
|
133
244
|
}
|
|
134
245
|
}
|
|
135
|
-
if (
|
|
136
|
-
await assertCurrent(
|
|
246
|
+
if (syncTempFile) {
|
|
247
|
+
await assertCurrent(tempPath);
|
|
137
248
|
await syncFileBestEffort(handle);
|
|
138
249
|
}
|
|
139
|
-
await assertCurrent(
|
|
140
|
-
await fs.rename(
|
|
250
|
+
await assertCurrent(tempPath);
|
|
251
|
+
await fs.rename(tempPath, filePath);
|
|
141
252
|
// A later verification failure never authorizes rollback of the final name.
|
|
142
253
|
renamed = true;
|
|
143
254
|
unregister();
|
|
144
255
|
await assertCurrent(filePath);
|
|
145
|
-
if (
|
|
256
|
+
if (syncParentDir)
|
|
146
257
|
await syncDirectoryBestEffort(parent);
|
|
147
258
|
await assertCurrent(filePath);
|
|
148
259
|
});
|
|
@@ -153,13 +264,22 @@ export async function writeCallbackSibling(params) {
|
|
|
153
264
|
throw error;
|
|
154
265
|
}
|
|
155
266
|
finally {
|
|
267
|
+
const settlementErrors = [];
|
|
268
|
+
if (cleanupWorkspace) {
|
|
269
|
+
try {
|
|
270
|
+
await cleanupWorkspace();
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
settlementErrors.push(error);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
156
276
|
try {
|
|
157
277
|
if (!renamed && identity) {
|
|
158
278
|
try {
|
|
159
279
|
await assertParent();
|
|
160
280
|
await inspectStage(() => fsSync.fstatSync(handle.fd, { bigint: true }), identity);
|
|
161
|
-
await inspectPath(
|
|
162
|
-
await fs.unlink(
|
|
281
|
+
await inspectPath(tempPath, identity);
|
|
282
|
+
await fs.unlink(tempPath);
|
|
163
283
|
unregister?.();
|
|
164
284
|
}
|
|
165
285
|
catch (error) {
|
|
@@ -175,9 +295,13 @@ export async function writeCallbackSibling(params) {
|
|
|
175
295
|
await handle?.close();
|
|
176
296
|
}
|
|
177
297
|
catch (error) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
298
|
+
settlementErrors.push(error);
|
|
299
|
+
}
|
|
300
|
+
if (settlementErrors.length > 0) {
|
|
301
|
+
if (failure) {
|
|
302
|
+
throw new AggregateError([failure.error, ...settlementErrors], "sibling publication and settlement failed");
|
|
303
|
+
}
|
|
304
|
+
throw aggregateErrors(settlementErrors, "sibling publication settlement failed");
|
|
181
305
|
}
|
|
182
306
|
}
|
|
183
307
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sibling-temp.d.ts","sourceRoot":"","sources":["../src/sibling-temp.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sibling-temp.d.ts","sourceRoot":"","sources":["../src/sibling-temp.ts"],"names":[],"mappings":"AAmBA,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5C,oEAAoE;IACpE,iBAAiB,CAAC,EAAE,mBAAmB,CAAC;IACxC,gBAAgB,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uGAAuG;IACvG,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,yEAAyE;IACzE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,0BAA0B,CAAC,CAAC,IAAI;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,CAAC,CAAC;CACX,CAAC;AASF,wBAAsB,oBAAoB,CAAC,CAAC,EAC1C,OAAO,EAAE,2BAA2B,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CA0BxC;AAwBD,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDhB"}
|
package/dist/sibling-temp.js
CHANGED
|
@@ -9,17 +9,22 @@ import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
|
|
|
9
9
|
import { root } from "./root.js";
|
|
10
10
|
import { assertSafePathPrefix } from "./safe-path-segment.js";
|
|
11
11
|
import { resolveSecureTempRoot } from "./secure-temp-dir.js";
|
|
12
|
-
import {
|
|
12
|
+
import { resolveCallbackTempPath, writeCallbackSibling } from "./sibling-staged-file.js";
|
|
13
13
|
import { tempFile } from "./temp-target.js";
|
|
14
14
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
15
|
-
|
|
15
|
+
import { assertNoWindowsPathAlias, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
16
|
+
function buildTempName(tempPrefix) {
|
|
16
17
|
const safePrefix = assertSafePathPrefix(tempPrefix ?? ".fs-safe-stream", {
|
|
17
18
|
label: "sibling temp prefix",
|
|
18
19
|
});
|
|
19
|
-
return
|
|
20
|
+
return `${safePrefix}.${process.pid}.${randomUUID()}.tmp`;
|
|
20
21
|
}
|
|
21
22
|
export async function writeSiblingTempFile(options) {
|
|
22
|
-
const
|
|
23
|
+
const dirInput = options.dir;
|
|
24
|
+
assertNoWindowsPathAlias(dirInput, "filesystem", "sibling temp directory uses a Windows filesystem namespace alias");
|
|
25
|
+
const dir = resolvePathPreservingWindowsRoot(dirInput);
|
|
26
|
+
assertNoWindowsPathAlias(dir, "filesystem", "sibling temp directory uses a Windows filesystem namespace alias");
|
|
27
|
+
const producerIsolation = options.producerIsolation;
|
|
23
28
|
await fs.mkdir(recursiveMkdirPath(dir), { recursive: true, mode: options.dirMode ?? 0o700 });
|
|
24
29
|
if (options.chmodDir !== false) {
|
|
25
30
|
await applyDirectoryMode({
|
|
@@ -30,9 +35,10 @@ export async function writeSiblingTempFile(options) {
|
|
|
30
35
|
});
|
|
31
36
|
}
|
|
32
37
|
return await writeCallbackSibling({
|
|
33
|
-
|
|
38
|
+
tempDir: dir,
|
|
39
|
+
tempName: buildTempName(options.tempPrefix),
|
|
34
40
|
write: options.writeTemp,
|
|
35
|
-
producerIsolation
|
|
41
|
+
producerIsolation,
|
|
36
42
|
resolveFinalPath: options.resolveFinalPath,
|
|
37
43
|
mode: options.mode,
|
|
38
44
|
ignoreModeError: true,
|
|
@@ -40,7 +46,7 @@ export async function writeSiblingTempFile(options) {
|
|
|
40
46
|
syncParentDir: options.syncParentDir === true,
|
|
41
47
|
});
|
|
42
48
|
}
|
|
43
|
-
function
|
|
49
|
+
function buildSiblingTempName(params) {
|
|
44
50
|
const id = crypto.randomUUID();
|
|
45
51
|
const safePrefix = assertSafePathPrefix(params.tempPrefix, {
|
|
46
52
|
label: "sibling temp prefix",
|
|
@@ -52,17 +58,23 @@ function buildSiblingTempPath(params) {
|
|
|
52
58
|
fileName: sanitizeUntrustedFileName(path.basename(params.targetPath), params.fallbackFileName),
|
|
53
59
|
suffix,
|
|
54
60
|
});
|
|
55
|
-
return
|
|
61
|
+
return `${prefix}${safeTail}${suffix}`;
|
|
56
62
|
}
|
|
57
63
|
export async function writeViaSiblingTempPath(params) {
|
|
64
|
+
const rootDirInput = params.rootDir;
|
|
65
|
+
const targetPathInput = params.targetPath;
|
|
66
|
+
assertNoWindowsPathAlias(rootDirInput, "filesystem", "sibling temp root uses a Windows filesystem namespace alias");
|
|
67
|
+
assertNoWindowsPathAlias(targetPathInput, "filesystem", "sibling temp target uses a Windows filesystem namespace alias");
|
|
58
68
|
let rootDir;
|
|
59
69
|
try {
|
|
60
|
-
rootDir = realpathSync.native(
|
|
70
|
+
rootDir = realpathSync.native(resolvePathPreservingWindowsRoot(rootDirInput));
|
|
61
71
|
}
|
|
62
72
|
catch {
|
|
63
|
-
rootDir =
|
|
73
|
+
rootDir = resolvePathPreservingWindowsRoot(rootDirInput);
|
|
64
74
|
}
|
|
65
|
-
|
|
75
|
+
assertNoWindowsPathAlias(rootDir, "filesystem", "sibling temp root uses a Windows filesystem namespace alias");
|
|
76
|
+
const requestedTargetPath = path.resolve(targetPathInput);
|
|
77
|
+
assertNoWindowsPathAlias(requestedTargetPath, "filesystem", "sibling temp target uses a Windows filesystem namespace alias");
|
|
66
78
|
let targetPath;
|
|
67
79
|
try {
|
|
68
80
|
const realDir = realpathSync.native(path.dirname(requestedTargetPath));
|
|
@@ -71,6 +83,7 @@ export async function writeViaSiblingTempPath(params) {
|
|
|
71
83
|
catch {
|
|
72
84
|
targetPath = requestedTargetPath;
|
|
73
85
|
}
|
|
86
|
+
assertNoWindowsPathAlias(targetPath, "filesystem", "sibling temp target uses a Windows filesystem namespace alias");
|
|
74
87
|
const relativeTargetPath = path.relative(rootDir, targetPath);
|
|
75
88
|
if (!relativeTargetPath ||
|
|
76
89
|
relativeTargetPath === ".." ||
|
|
@@ -87,13 +100,14 @@ export async function writeViaSiblingTempPath(params) {
|
|
|
87
100
|
}),
|
|
88
101
|
prefix: "fs-safe-output",
|
|
89
102
|
});
|
|
103
|
+
assertNoWindowsPathAlias(workspace.dir, "filesystem", "sibling temp workspace uses a Windows filesystem namespace alias");
|
|
90
104
|
try {
|
|
91
|
-
const
|
|
105
|
+
const tempName = buildSiblingTempName({
|
|
92
106
|
targetPath: path.join(workspace.dir, path.basename(targetPath)),
|
|
93
107
|
fallbackFileName: params.fallbackFileName ?? "output.bin",
|
|
94
108
|
tempPrefix: params.tempPrefix ?? ".fs-safe-output-",
|
|
95
109
|
});
|
|
96
|
-
|
|
110
|
+
const tempPath = resolveCallbackTempPath(workspace.dir, tempName);
|
|
97
111
|
await getFsSafeTestHooks()?.beforeSiblingTempWrite?.(tempPath);
|
|
98
112
|
await params.writeTemp(tempPath);
|
|
99
113
|
await assertAsyncDirectoryGuard(rootGuard);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidecar-lock-acquire.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-acquire.ts"],"names":[],"mappings":"AAUA,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAW3C,OAAO,EAWL,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"sidecar-lock-acquire.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-acquire.ts"],"names":[],"mappings":"AAUA,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAW3C,OAAO,EAWL,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAK5F,KAAK,iBAAiB,GAAG,IAAI,CAAC,gBAAgB,EAAE,IAAI,GAAG,OAAO,GAAG,MAAM,GAAG,WAAW,CAAC,CAAC;AAEvF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,eAAe,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;CAClC,CAAC;AAEF,KAAK,6BAA6B,GAAG;IACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACnC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,2BAA2B,IAAI,IAAI,CAAC;IACpC,iBAAiB,CAAC,oBAAoB,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,iBAAiB,CAAC;IAC1F,eAAe,CACb,oBAAoB,EAAE,MAAM,EAC5B,IAAI,EAAE,eAAe,EACrB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5B,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB,CAAC;AAoCF,wBAAsB,kBAAkB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/E,OAAO,EAAE,yBAAyB,CAAC,QAAQ,CAAC,EAC5C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,iBAAiB,CAAC,CA2U5B"}
|