@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
|
@@ -1,20 +1,19 @@
|
|
|
1
|
-
import syncFs from "node:fs";
|
|
1
|
+
import syncFs, {} from "node:fs";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { createByteLimitTransform } from "./bounded-read-stream.js";
|
|
5
5
|
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
6
6
|
import { pipeline } from "node:stream/promises";
|
|
7
|
-
import { assertSyncDirectoryGuard as assertDirectoryGuardSync, createSyncDirectoryGuard, } from "./directory-guard.js";
|
|
8
7
|
import { FsSafeError } from "./errors.js";
|
|
9
|
-
import {
|
|
10
|
-
import { isPathInside
|
|
8
|
+
import { assertSyncStoreDirectoryReceipt, ensureSyncStoreDirectory, } from "./file-store-sync-directory.js";
|
|
9
|
+
import { isPathInside } from "./path.js";
|
|
11
10
|
import { resolveOpenedFileRealPathForHandle, root } from "./root.js";
|
|
12
11
|
import { ensureTrailingSep } from "./root-context.js";
|
|
13
12
|
import { RootHandle } from "./root-impl.js";
|
|
14
13
|
import { prepareSecretFileWrite } from "./secret-file.js";
|
|
15
14
|
import { resolveSecureTempRoot } from "./secure-temp-dir.js";
|
|
16
|
-
import { realpathSync } from "./realpath.js";
|
|
17
15
|
import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
|
|
16
|
+
import { assertNoWindowsPathAlias, pathForWindowsFilesystem } from "./windows-path-alias.js";
|
|
18
17
|
function parentRelativePath(relativePath) {
|
|
19
18
|
const parent = path.posix.dirname(relativePath);
|
|
20
19
|
return parent === "." ? "" : parent;
|
|
@@ -28,6 +27,7 @@ export async function ensureParentInRoot(scopedRoot, relativePath, mode) {
|
|
|
28
27
|
await chmodDirectoryInRootBestEffort(scopedRoot, parent, mode).catch(() => undefined);
|
|
29
28
|
}
|
|
30
29
|
export async function openWritableStoreRoot(params) {
|
|
30
|
+
assertNoWindowsPathAlias(params.rootDir, "filesystem", "store root uses a Windows filesystem namespace alias");
|
|
31
31
|
const maxBytes = normalizeMaxBytes(params.maxBytes);
|
|
32
32
|
await fs.mkdir(recursiveMkdirPath(params.rootDir), { recursive: true, mode: params.dirMode });
|
|
33
33
|
await fs.chmod(params.rootDir, params.dirMode).catch(() => undefined);
|
|
@@ -38,6 +38,7 @@ export async function openPrivateStoreLockRoot(params) {
|
|
|
38
38
|
// Bind to the admitted parent, never resolve a replacement into a fresh capability.
|
|
39
39
|
return new RootHandle({
|
|
40
40
|
rootDir: parentGuard.dir,
|
|
41
|
+
rootGuard: { dir: parentGuard.realPath, realPath: parentGuard.realPath, stat: parentGuard.stat },
|
|
41
42
|
rootReal: parentGuard.realPath,
|
|
42
43
|
rootWithSep: ensureTrailingSep(parentGuard.realPath),
|
|
43
44
|
rootIdentity: { dev: parentGuard.stat.dev, ino: parentGuard.stat.ino },
|
|
@@ -116,27 +117,15 @@ export async function writeStreamToTempSource(params) {
|
|
|
116
117
|
}
|
|
117
118
|
}
|
|
118
119
|
export function assertSyncDirectoryGuard(guard) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
throw new FsSafeError("path-mismatch", "store directory changed during write", {
|
|
125
|
-
cause: error,
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
throw error;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
function chmodDirectorySyncBestEffort(dir, mode) {
|
|
132
|
-
try {
|
|
133
|
-
syncFs.chmodSync(dir, mode);
|
|
134
|
-
}
|
|
135
|
-
catch {
|
|
136
|
-
// Best-effort on platforms that do not enforce POSIX modes.
|
|
137
|
-
}
|
|
120
|
+
assertSyncStoreDirectoryReceipt({
|
|
121
|
+
dir: guard.dir,
|
|
122
|
+
realPath: guard.realPath,
|
|
123
|
+
exactStat: guard.exactStat,
|
|
124
|
+
});
|
|
138
125
|
}
|
|
139
126
|
export function ensureParentSync(params) {
|
|
127
|
+
assertNoWindowsPathAlias(params.rootDir, "filesystem", "store root uses a Windows filesystem namespace alias");
|
|
128
|
+
assertNoWindowsPathAlias(params.filePath, "filesystem", "store path uses a Windows filesystem namespace alias");
|
|
140
129
|
return ensureStoreDirectorySync({
|
|
141
130
|
rootDir: params.rootDir,
|
|
142
131
|
targetDir: path.dirname(path.resolve(params.filePath)),
|
|
@@ -145,47 +134,13 @@ export function ensureParentSync(params) {
|
|
|
145
134
|
});
|
|
146
135
|
}
|
|
147
136
|
export function ensureStoreDirectorySync(params) {
|
|
148
|
-
const
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const rootStat = syncFs.lstatSync(rootDir);
|
|
156
|
-
if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
|
|
157
|
-
throw new FsSafeError("not-file", `${params.messagePrefix} root must be a directory: ${rootDir}`);
|
|
158
|
-
}
|
|
159
|
-
const rootReal = realpathSync(rootDir);
|
|
160
|
-
chmodDirectorySyncBestEffort(rootDir, params.mode);
|
|
161
|
-
let current = rootDir;
|
|
162
|
-
for (const segment of path.relative(rootDir, dir).split(path.sep).filter(Boolean)) {
|
|
163
|
-
current = path.join(current, segment);
|
|
164
|
-
try {
|
|
165
|
-
const stat = syncFs.lstatSync(current);
|
|
166
|
-
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
167
|
-
throw new FsSafeError("not-file", `${params.messagePrefix} directory component must be a directory: ${current}`);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
catch (error) {
|
|
171
|
-
if (error.code !== "ENOENT") {
|
|
172
|
-
throw error;
|
|
173
|
-
}
|
|
174
|
-
syncFs.mkdirSync(current, { mode: params.mode });
|
|
175
|
-
}
|
|
176
|
-
const currentRootStat = syncFs.lstatSync(rootDir);
|
|
177
|
-
const currentRootReal = realpathSync(rootDir);
|
|
178
|
-
const currentReal = realpathSync(current);
|
|
179
|
-
if (currentRootStat.isSymbolicLink() ||
|
|
180
|
-
!currentRootStat.isDirectory() ||
|
|
181
|
-
!sameFileIdentity(rootStat, currentRootStat) ||
|
|
182
|
-
currentRootReal !== rootReal ||
|
|
183
|
-
!isPathInside(rootReal, currentReal)) {
|
|
184
|
-
throw new FsSafeError("outside-workspace", `${params.messagePrefix} directory escapes root`);
|
|
185
|
-
}
|
|
186
|
-
chmodDirectorySyncBestEffort(current, params.mode);
|
|
187
|
-
}
|
|
188
|
-
const guard = createSyncDirectoryGuard(dir);
|
|
137
|
+
const receipt = ensureSyncStoreDirectory(params);
|
|
138
|
+
const guard = {
|
|
139
|
+
dir: receipt.dir,
|
|
140
|
+
realPath: receipt.realPath,
|
|
141
|
+
stat: syncFs.lstatSync(pathForWindowsFilesystem(receipt.dir)),
|
|
142
|
+
exactStat: receipt.exactStat,
|
|
143
|
+
};
|
|
189
144
|
assertSyncDirectoryGuard(guard);
|
|
190
145
|
return guard;
|
|
191
146
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-store-copy-source.d.ts","sourceRoot":"","sources":["../src/file-store-copy-source.ts"],"names":[],"mappings":"AAMA,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,MAAM,CAAC,CA4BlB"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { FsSafeError } from "./errors.js";
|
|
3
|
+
import { assertFileStoreMaxBytes } from "./file-store-limit.js";
|
|
4
|
+
import { readRegularFile } from "./regular-file.js";
|
|
5
|
+
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
6
|
+
export async function readFileStoreCopySource(params) {
|
|
7
|
+
assertNoWindowsPathAlias(params.sourcePath, "filesystem", "source path uses a Windows filesystem namespace alias");
|
|
8
|
+
const sourceStat = fs.lstatSync(params.sourcePath);
|
|
9
|
+
if (sourceStat.isSymbolicLink() || !sourceStat.isFile()) {
|
|
10
|
+
throw new FsSafeError("not-file", "source path is not a file");
|
|
11
|
+
}
|
|
12
|
+
assertFileStoreMaxBytes(sourceStat.size, params.maxBytes);
|
|
13
|
+
try {
|
|
14
|
+
return (await readRegularFile({ filePath: params.sourcePath, maxBytes: params.maxBytes }))
|
|
15
|
+
.buffer;
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
19
|
+
if (message.includes("regular file") || message.includes("not a regular file")) {
|
|
20
|
+
throw new FsSafeError("not-file", "source path is not a file", {
|
|
21
|
+
cause: error instanceof Error ? error : undefined,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
if (params.maxBytes !== undefined && message.includes(`exceeds ${params.maxBytes} bytes`)) {
|
|
25
|
+
throw new FsSafeError("too-large", `file exceeds maximum size of ${params.maxBytes} bytes`, {
|
|
26
|
+
cause: error instanceof Error ? error : undefined,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-store-path.d.ts","sourceRoot":"","sources":["../src/file-store-path.ts"],"names":[],"mappings":"AAIA,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAe/D;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"file-store-path.d.ts","sourceRoot":"","sources":["../src/file-store-path.ts"],"names":[],"mappings":"AAIA,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAe/D;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAY9E"}
|
package/dist/file-store-path.js
CHANGED
|
@@ -17,7 +17,10 @@ export function assertRelativePath(relativePath) {
|
|
|
17
17
|
}
|
|
18
18
|
export function resolveStorePath(rootDir, relativePath) {
|
|
19
19
|
const key = assertRelativePath(relativePath);
|
|
20
|
-
|
|
20
|
+
// FileStore constructors snapshot an absolute root before this helper runs.
|
|
21
|
+
// Do not re-resolve it: Node drops the trailing separator from an exact
|
|
22
|
+
// Windows namespace drive root such as `\\?\C:\`.
|
|
23
|
+
const root = path.isAbsolute(rootDir) ? rootDir : path.resolve(rootDir);
|
|
21
24
|
// The immutable key already passed segment validation; keep the containment check.
|
|
22
25
|
const target = path.resolve(root, key);
|
|
23
26
|
if (!isPathInside(root, target)) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type BigIntStats } from "node:fs";
|
|
2
|
+
type StoreMessagePrefix = "private store" | "store";
|
|
3
|
+
export type SyncStoreDirectoryReceipt = Readonly<{
|
|
4
|
+
dir: string;
|
|
5
|
+
realPath: string;
|
|
6
|
+
exactStat: BigIntStats;
|
|
7
|
+
}>;
|
|
8
|
+
export declare function ensureSyncStoreDirectory(params: {
|
|
9
|
+
rootDir: string;
|
|
10
|
+
targetDir: string;
|
|
11
|
+
mode: number;
|
|
12
|
+
messagePrefix: StoreMessagePrefix;
|
|
13
|
+
}): SyncStoreDirectoryReceipt;
|
|
14
|
+
export declare function assertSyncStoreDirectoryReceipt(receipt: SyncStoreDirectoryReceipt): void;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=file-store-sync-directory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-store-sync-directory.d.ts","sourceRoot":"","sources":["../src/file-store-sync-directory.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAc/C,KAAK,kBAAkB,GAAG,eAAe,GAAG,OAAO,CAAC;AAOpD,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,WAAW,CAAC;CACxB,CAAC,CAAC;AAkVH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,kBAAkB,CAAC;CACnC,GAAG,yBAAyB,CA4D5B;AAED,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,yBAAyB,GACjC,IAAI,CAsBN"}
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import fs, {} from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { FsSafeError } from "./errors.js";
|
|
4
|
+
import { isPathInside, isPathRelativeEscape } from "./path.js";
|
|
5
|
+
import { realpathSync } from "./realpath.js";
|
|
6
|
+
import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
|
|
7
|
+
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
8
|
+
import { createSuppressedError } from "./suppressed-error.js";
|
|
9
|
+
import { assertNoWindowsPathAlias, pathForWindowsFilesystem, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
10
|
+
function changedStoreDirectory(messagePrefix, cause) {
|
|
11
|
+
return new FsSafeError("outside-workspace", `${messagePrefix} directory escapes root`, { cause });
|
|
12
|
+
}
|
|
13
|
+
function storeDirectoryRealPath(dir) {
|
|
14
|
+
const realPath = realpathSync(pathForWindowsFilesystem(dir));
|
|
15
|
+
assertNoWindowsPathAlias(realPath, "filesystem");
|
|
16
|
+
return realPath;
|
|
17
|
+
}
|
|
18
|
+
function inspectStoreDirectory(params) {
|
|
19
|
+
assertNoWindowsPathAlias(params.dir, "filesystem");
|
|
20
|
+
return inspectFileIdentitySync(() => {
|
|
21
|
+
const stat = fs.lstatSync(pathForWindowsFilesystem(params.dir), { bigint: true });
|
|
22
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
23
|
+
const subject = params.root ? "root" : "directory component";
|
|
24
|
+
throw new FsSafeError("not-file", `${params.messagePrefix} ${subject} must be a directory: ${params.dir}`);
|
|
25
|
+
}
|
|
26
|
+
return stat;
|
|
27
|
+
}, params.expected);
|
|
28
|
+
}
|
|
29
|
+
function observeStoreDirectory(params) {
|
|
30
|
+
return {
|
|
31
|
+
dir: params.dir,
|
|
32
|
+
stat: inspectStoreDirectory(params),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function inspectReceiptCurrent(receipt, messagePrefix, root) {
|
|
36
|
+
try {
|
|
37
|
+
const stat = inspectStoreDirectory({
|
|
38
|
+
dir: receipt.dir,
|
|
39
|
+
expected: receipt.stat,
|
|
40
|
+
messagePrefix,
|
|
41
|
+
root,
|
|
42
|
+
});
|
|
43
|
+
if (stat.dev !== receipt.stat.dev || stat.ino !== receipt.stat.ino) {
|
|
44
|
+
throw changedStoreDirectory(messagePrefix);
|
|
45
|
+
}
|
|
46
|
+
return stat;
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (error instanceof FsSafeError && error.code === "outside-workspace")
|
|
50
|
+
throw error;
|
|
51
|
+
throw changedStoreDirectory(messagePrefix, error);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function assertRootCurrent(rootReceipt, rootReal, messagePrefix) {
|
|
55
|
+
const stat = inspectReceiptCurrent(rootReceipt, messagePrefix, true);
|
|
56
|
+
try {
|
|
57
|
+
if (storeDirectoryRealPath(rootReceipt.dir) !== rootReal) {
|
|
58
|
+
throw changedStoreDirectory(messagePrefix);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (error instanceof FsSafeError && error.code === "outside-workspace")
|
|
63
|
+
throw error;
|
|
64
|
+
throw changedStoreDirectory(messagePrefix, error);
|
|
65
|
+
}
|
|
66
|
+
return stat;
|
|
67
|
+
}
|
|
68
|
+
function assertAdmittedEdgeCurrent(receipts, rootReal, messagePrefix) {
|
|
69
|
+
const rootReceipt = receipts[0];
|
|
70
|
+
const targetIndex = receipts.length - 1;
|
|
71
|
+
const targetReceipt = receipts[targetIndex];
|
|
72
|
+
if (!rootReceipt || !targetReceipt) {
|
|
73
|
+
throw new FsSafeError("helper-failed", "store directory receipt is missing");
|
|
74
|
+
}
|
|
75
|
+
const rootStat = assertRootCurrent(rootReceipt, rootReal, messagePrefix);
|
|
76
|
+
if (targetIndex === 0)
|
|
77
|
+
return rootStat;
|
|
78
|
+
const parent = receipts[targetIndex - 1];
|
|
79
|
+
if (!parent)
|
|
80
|
+
throw new FsSafeError("helper-failed", "store directory receipt is missing");
|
|
81
|
+
if (targetIndex > 1)
|
|
82
|
+
inspectReceiptCurrent(parent, messagePrefix, false);
|
|
83
|
+
const targetStat = inspectReceiptCurrent(targetReceipt, messagePrefix, false);
|
|
84
|
+
if (path.dirname(targetReceipt.dir) !== parent.dir) {
|
|
85
|
+
throw changedStoreDirectory(messagePrefix);
|
|
86
|
+
}
|
|
87
|
+
return targetStat;
|
|
88
|
+
}
|
|
89
|
+
function assertModeTargetCurrent(receipts, rootReal, messagePrefix) {
|
|
90
|
+
const target = receipts.at(-1);
|
|
91
|
+
if (!target)
|
|
92
|
+
throw new FsSafeError("helper-failed", "store directory receipt is missing");
|
|
93
|
+
const stat = assertAdmittedEdgeCurrent(receipts, rootReal, messagePrefix);
|
|
94
|
+
let targetReal;
|
|
95
|
+
try {
|
|
96
|
+
targetReal = storeDirectoryRealPath(target.dir);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
throw changedStoreDirectory(messagePrefix, error);
|
|
100
|
+
}
|
|
101
|
+
if (!isPathInside(rootReal, targetReal))
|
|
102
|
+
throw changedStoreDirectory(messagePrefix);
|
|
103
|
+
assertAdmittedEdgeCurrent(receipts, rootReal, messagePrefix);
|
|
104
|
+
return stat;
|
|
105
|
+
}
|
|
106
|
+
function assertReceiptChain(receipts, rootReal, messagePrefix) {
|
|
107
|
+
const rootReceipt = receipts[0];
|
|
108
|
+
const finalReceipt = receipts.at(-1);
|
|
109
|
+
if (!rootReceipt || !finalReceipt) {
|
|
110
|
+
throw new FsSafeError("helper-failed", "store directory receipt is missing");
|
|
111
|
+
}
|
|
112
|
+
assertRootCurrent(rootReceipt, rootReal, messagePrefix);
|
|
113
|
+
let finalStat = rootReceipt.stat;
|
|
114
|
+
for (const [index, receipt] of receipts.entries()) {
|
|
115
|
+
if (index === 0)
|
|
116
|
+
continue;
|
|
117
|
+
finalStat = inspectReceiptCurrent(receipt, messagePrefix, false);
|
|
118
|
+
}
|
|
119
|
+
let finalReal;
|
|
120
|
+
try {
|
|
121
|
+
finalReal = storeDirectoryRealPath(finalReceipt.dir);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
throw changedStoreDirectory(messagePrefix, error);
|
|
125
|
+
}
|
|
126
|
+
if (!isPathInside(rootReal, finalReal))
|
|
127
|
+
throw changedStoreDirectory(messagePrefix);
|
|
128
|
+
assertRootCurrent(rootReceipt, rootReal, messagePrefix);
|
|
129
|
+
if (finalReceipt !== rootReceipt) {
|
|
130
|
+
finalStat = inspectReceiptCurrent(finalReceipt, messagePrefix, false);
|
|
131
|
+
}
|
|
132
|
+
return { finalReal, finalStat };
|
|
133
|
+
}
|
|
134
|
+
function directoryOpenFlags() {
|
|
135
|
+
const { O_DIRECTORY, O_NOFOLLOW, O_NONBLOCK, O_RDONLY } = fs.constants;
|
|
136
|
+
if ([O_DIRECTORY, O_NOFOLLOW, O_NONBLOCK, O_RDONLY]
|
|
137
|
+
.some((flag) => typeof flag !== "number")) {
|
|
138
|
+
throw new FsSafeError("helper-unavailable", "no-follow directory mode descriptors are unavailable");
|
|
139
|
+
}
|
|
140
|
+
return O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_NONBLOCK;
|
|
141
|
+
}
|
|
142
|
+
function directorySearchFlags() {
|
|
143
|
+
// Linux O_PATH descriptors cannot be fchmoded, and /proc/self/fd would
|
|
144
|
+
// reintroduce a pathname mutation. Darwin O_SEARCH remains descriptor-bound.
|
|
145
|
+
if (process.platform !== "darwin" ||
|
|
146
|
+
(process.arch !== "x64" && process.arch !== "arm64"))
|
|
147
|
+
return undefined;
|
|
148
|
+
const { O_DIRECTORY, O_NOFOLLOW, O_NONBLOCK } = fs.constants;
|
|
149
|
+
if ([O_DIRECTORY, O_NOFOLLOW, O_NONBLOCK].some((flag) => typeof flag !== "number")) {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
// Darwin SDK O_SEARCH = O_EXEC (0x40000000) | O_DIRECTORY on supported Node hosts.
|
|
153
|
+
return 0x40000000 | O_DIRECTORY | O_NOFOLLOW | O_NONBLOCK;
|
|
154
|
+
}
|
|
155
|
+
function hasErrorCode(error, code) {
|
|
156
|
+
if (error && typeof error === "object") {
|
|
157
|
+
if (error.code === code)
|
|
158
|
+
return true;
|
|
159
|
+
const combined = error;
|
|
160
|
+
return (combined.error !== error && hasErrorCode(combined.error, code)) ||
|
|
161
|
+
(combined.suppressed !== error && hasErrorCode(combined.suppressed, code));
|
|
162
|
+
}
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
function openStoreDirectoryForMode(params) {
|
|
166
|
+
let openError;
|
|
167
|
+
try {
|
|
168
|
+
return fs.openSync(pathForWindowsFilesystem(params.receipt.dir), directoryOpenFlags());
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
openError = error;
|
|
172
|
+
}
|
|
173
|
+
const searchFlags = hasErrorCode(openError, "EACCES") ? directorySearchFlags() : undefined;
|
|
174
|
+
if (searchFlags !== undefined) {
|
|
175
|
+
try {
|
|
176
|
+
return fs.openSync(pathForWindowsFilesystem(params.receipt.dir), searchFlags);
|
|
177
|
+
}
|
|
178
|
+
catch (searchError) {
|
|
179
|
+
openError = createSuppressedError(searchError, openError, "ordinary and search-only directory opens both failed");
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
assertModeTargetCurrent(params.receipts, params.rootReal, params.messagePrefix);
|
|
184
|
+
}
|
|
185
|
+
catch (boundaryError) {
|
|
186
|
+
throw changedStoreDirectory(params.messagePrefix, createSuppressedError(boundaryError, openError, "directory acquisition and boundary revalidation both failed"));
|
|
187
|
+
}
|
|
188
|
+
if (hasErrorCode(openError, "EACCES")) {
|
|
189
|
+
throw new FsSafeError("permission-unverified", `${params.messagePrefix} directory cannot be safely mode-repaired through a Node descriptor`, { cause: openError });
|
|
190
|
+
}
|
|
191
|
+
throw openError;
|
|
192
|
+
}
|
|
193
|
+
function requestedDirectoryMode(mode) {
|
|
194
|
+
return BigInt(mode & 0o7777);
|
|
195
|
+
}
|
|
196
|
+
function inspectOpenedStoreDirectory(descriptor, receipt, messagePrefix) {
|
|
197
|
+
try {
|
|
198
|
+
const stat = inspectFileIdentitySync(() => fs.fstatSync(descriptor, { bigint: true }), receipt.stat);
|
|
199
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
200
|
+
throw changedStoreDirectory(messagePrefix);
|
|
201
|
+
}
|
|
202
|
+
return stat;
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
if (error instanceof FsSafeError && error.code === "outside-workspace")
|
|
206
|
+
throw error;
|
|
207
|
+
throw changedStoreDirectory(messagePrefix, error);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function finalizeDirectoryMode(params) {
|
|
211
|
+
const receipt = params.receipts.at(-1);
|
|
212
|
+
if (!receipt)
|
|
213
|
+
throw new FsSafeError("helper-failed", "store directory receipt is missing");
|
|
214
|
+
const requestedMode = requestedDirectoryMode(params.mode);
|
|
215
|
+
if ((receipt.stat.mode & 4095n) === requestedMode || process.platform === "win32") {
|
|
216
|
+
const current = assertAdmittedEdgeCurrent(params.receipts, params.rootReal, params.messagePrefix);
|
|
217
|
+
// Identity can remain stable while permissions change after the first receipt.
|
|
218
|
+
if (process.platform === "win32" || (current.mode & 4095n) === requestedMode) {
|
|
219
|
+
return { ...receipt, stat: current };
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// Repair owns the stronger pre/post mode-target fence below, including an
|
|
223
|
+
// admission check on acquisition failure; no earlier admission is needed.
|
|
224
|
+
const descriptor = openStoreDirectoryForMode({
|
|
225
|
+
receipt,
|
|
226
|
+
receipts: params.receipts,
|
|
227
|
+
rootReal: params.rootReal,
|
|
228
|
+
messagePrefix: params.messagePrefix,
|
|
229
|
+
});
|
|
230
|
+
let finalStat;
|
|
231
|
+
let operationFailed = false;
|
|
232
|
+
let operationError;
|
|
233
|
+
try {
|
|
234
|
+
const opened = inspectOpenedStoreDirectory(descriptor, receipt, params.messagePrefix);
|
|
235
|
+
assertModeTargetCurrent(params.receipts, params.rootReal, params.messagePrefix);
|
|
236
|
+
if ((opened.mode & 4095n) !== requestedMode) {
|
|
237
|
+
fs.fchmodSync(descriptor, Number(requestedMode));
|
|
238
|
+
}
|
|
239
|
+
const finalized = inspectOpenedStoreDirectory(descriptor, receipt, params.messagePrefix);
|
|
240
|
+
const pathname = assertModeTargetCurrent(params.receipts, params.rootReal, params.messagePrefix);
|
|
241
|
+
if ((finalized.mode & 4095n) !== requestedMode ||
|
|
242
|
+
(pathname.mode & 4095n) !== requestedMode) {
|
|
243
|
+
throw new FsSafeError("insecure-permissions", `${params.messagePrefix} directory mode could not be finalized`);
|
|
244
|
+
}
|
|
245
|
+
finalStat = pathname;
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
operationFailed = true;
|
|
249
|
+
operationError = error;
|
|
250
|
+
}
|
|
251
|
+
try {
|
|
252
|
+
fs.closeSync(descriptor);
|
|
253
|
+
}
|
|
254
|
+
catch (closeError) {
|
|
255
|
+
if (operationFailed) {
|
|
256
|
+
throw createSuppressedError(closeError, operationError, "store directory finalization and close both failed");
|
|
257
|
+
}
|
|
258
|
+
throw closeError;
|
|
259
|
+
}
|
|
260
|
+
if (operationFailed)
|
|
261
|
+
throw operationError;
|
|
262
|
+
if (!finalStat)
|
|
263
|
+
throw new FsSafeError("helper-failed", "store directory mode receipt is missing");
|
|
264
|
+
return { ...receipt, stat: finalStat };
|
|
265
|
+
}
|
|
266
|
+
export function ensureSyncStoreDirectory(params) {
|
|
267
|
+
assertNoWindowsPathAlias(params.rootDir, "filesystem", "store root uses a Windows filesystem namespace alias");
|
|
268
|
+
assertNoWindowsPathAlias(params.targetDir, "filesystem", "store path uses a Windows filesystem namespace alias");
|
|
269
|
+
const rootDir = resolvePathPreservingWindowsRoot(params.rootDir);
|
|
270
|
+
const dir = resolvePathPreservingWindowsRoot(params.targetDir);
|
|
271
|
+
assertNoWindowsPathAlias(rootDir, "filesystem", "store root uses a Windows filesystem namespace alias");
|
|
272
|
+
assertNoWindowsPathAlias(dir, "filesystem", "store path uses a Windows filesystem namespace alias");
|
|
273
|
+
const relative = path.relative(rootDir, dir);
|
|
274
|
+
if (isPathRelativeEscape(relative)) {
|
|
275
|
+
throw new FsSafeError("outside-workspace", "file path escapes store root");
|
|
276
|
+
}
|
|
277
|
+
fs.mkdirSync(recursiveMkdirPath(pathForWindowsFilesystem(rootDir)), { recursive: true, mode: params.mode });
|
|
278
|
+
const receipts = [observeStoreDirectory({
|
|
279
|
+
dir: rootDir,
|
|
280
|
+
messagePrefix: params.messagePrefix,
|
|
281
|
+
root: true,
|
|
282
|
+
})];
|
|
283
|
+
const rootReal = storeDirectoryRealPath(rootDir);
|
|
284
|
+
receipts[0] = finalizeDirectoryMode({
|
|
285
|
+
receipts,
|
|
286
|
+
rootReal,
|
|
287
|
+
mode: params.mode,
|
|
288
|
+
messagePrefix: params.messagePrefix,
|
|
289
|
+
});
|
|
290
|
+
let current = rootDir;
|
|
291
|
+
for (const segment of relative.split(path.sep).filter(Boolean)) {
|
|
292
|
+
current = path.join(current, segment);
|
|
293
|
+
let receipt;
|
|
294
|
+
try {
|
|
295
|
+
receipt = observeStoreDirectory({
|
|
296
|
+
dir: current,
|
|
297
|
+
messagePrefix: params.messagePrefix,
|
|
298
|
+
root: false,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
catch (error) {
|
|
302
|
+
if (error.code !== "ENOENT")
|
|
303
|
+
throw error;
|
|
304
|
+
fs.mkdirSync(pathForWindowsFilesystem(current), { mode: params.mode });
|
|
305
|
+
receipt = observeStoreDirectory({
|
|
306
|
+
dir: current,
|
|
307
|
+
messagePrefix: params.messagePrefix,
|
|
308
|
+
root: false,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
receipts.push(receipt);
|
|
312
|
+
receipts[receipts.length - 1] = finalizeDirectoryMode({
|
|
313
|
+
receipts,
|
|
314
|
+
rootReal,
|
|
315
|
+
mode: params.mode,
|
|
316
|
+
messagePrefix: params.messagePrefix,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
const finalChain = assertReceiptChain(receipts, rootReal, params.messagePrefix);
|
|
320
|
+
return {
|
|
321
|
+
dir,
|
|
322
|
+
realPath: finalChain.finalReal,
|
|
323
|
+
exactStat: finalChain.finalStat,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
export function assertSyncStoreDirectoryReceipt(receipt) {
|
|
327
|
+
try {
|
|
328
|
+
assertNoWindowsPathAlias(receipt.realPath, "filesystem");
|
|
329
|
+
const stat = inspectStoreDirectory({
|
|
330
|
+
dir: receipt.dir,
|
|
331
|
+
expected: receipt.exactStat,
|
|
332
|
+
messagePrefix: "store",
|
|
333
|
+
root: false,
|
|
334
|
+
});
|
|
335
|
+
if (stat.dev !== receipt.exactStat.dev || stat.ino !== receipt.exactStat.ino ||
|
|
336
|
+
storeDirectoryRealPath(receipt.dir) !== receipt.realPath) {
|
|
337
|
+
throw new FsSafeError("path-mismatch", "store directory changed during write");
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
if (error instanceof FsSafeError && error.code === "path-mismatch" &&
|
|
342
|
+
error.message !== "store directory changed during write") {
|
|
343
|
+
throw new FsSafeError("path-mismatch", "store directory changed during write", {
|
|
344
|
+
cause: error,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
throw error;
|
|
348
|
+
}
|
|
349
|
+
}
|
package/dist/file-store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-store.d.ts","sourceRoot":"","sources":["../src/file-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAM5C,OAAO,EAA4B,KAAK,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC7F,YAAY,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAQnE,OAAO,EAAmB,KAAK,oBAAoB,EAAE,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAItG,OAAO,EAAQ,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,IAAI,EAAE,KAAK,eAAe,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"file-store.d.ts","sourceRoot":"","sources":["../src/file-store.ts"],"names":[],"mappings":"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"}
|
package/dist/file-store.js
CHANGED
|
@@ -15,9 +15,10 @@ import { isNotFoundPathError } from "./path.js";
|
|
|
15
15
|
import { throwFsSafeReadError } from "./root-errors.js";
|
|
16
16
|
import { root } from "./root.js";
|
|
17
17
|
import { DEFAULT_ROOT_MAX_BYTES } from "./root-impl.js";
|
|
18
|
-
import { readRegularFile } from "./regular-file.js";
|
|
19
18
|
import { matchRootFileOpenFailure, openRootFileSync } from "./root-file.js";
|
|
20
19
|
import { writeSecretFileAtomic } from "./secret-file.js";
|
|
20
|
+
import { assertNoWindowsPathAlias, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
21
|
+
import { readFileStoreCopySource } from "./file-store-copy-source.js";
|
|
21
22
|
function isNotFound(error) {
|
|
22
23
|
return error instanceof FsSafeError ? error.code === "not-found" : isNotFoundPathError(error);
|
|
23
24
|
}
|
|
@@ -49,34 +50,10 @@ function handleSyncStoreReadOpenFailure(opened) {
|
|
|
49
50
|
},
|
|
50
51
|
});
|
|
51
52
|
}
|
|
52
|
-
async function readFileStoreCopySource(params) {
|
|
53
|
-
const sourceStat = syncFs.lstatSync(params.sourcePath);
|
|
54
|
-
if (sourceStat.isSymbolicLink() || !sourceStat.isFile()) {
|
|
55
|
-
throw new FsSafeError("not-file", "source path is not a file");
|
|
56
|
-
}
|
|
57
|
-
assertFileStoreMaxBytes(sourceStat.size, params.maxBytes);
|
|
58
|
-
try {
|
|
59
|
-
return (await readRegularFile({ filePath: params.sourcePath, maxBytes: params.maxBytes }))
|
|
60
|
-
.buffer;
|
|
61
|
-
}
|
|
62
|
-
catch (error) {
|
|
63
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
64
|
-
if (message.includes("regular file") || message.includes("not a regular file")) {
|
|
65
|
-
throw new FsSafeError("not-file", "source path is not a file", {
|
|
66
|
-
cause: error instanceof Error ? error : undefined,
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
if (params.maxBytes !== undefined && message.includes(`exceeds ${params.maxBytes} bytes`)) {
|
|
70
|
-
throw new FsSafeError("too-large", `file exceeds maximum size of ${params.maxBytes} bytes`, {
|
|
71
|
-
cause: error instanceof Error ? error : undefined,
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
throw error;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
53
|
async function copyIntoRoot(params) {
|
|
78
54
|
const relativePath = assertRelativePath(params.relativePath);
|
|
79
55
|
const destination = resolveStorePath(params.rootDir, relativePath);
|
|
56
|
+
assertNoWindowsPathAlias(params.sourcePath, "filesystem", "source path uses a Windows filesystem namespace alias");
|
|
80
57
|
const sourceStat = syncFs.lstatSync(params.sourcePath);
|
|
81
58
|
if (sourceStat.isSymbolicLink() || !sourceStat.isFile()) {
|
|
82
59
|
throw new FsSafeError("not-file", "source path is not a file");
|
|
@@ -98,7 +75,10 @@ async function copyIntoRoot(params) {
|
|
|
98
75
|
return destination;
|
|
99
76
|
}
|
|
100
77
|
export function fileStore(options) {
|
|
101
|
-
const
|
|
78
|
+
const rootDirInput = options.rootDir;
|
|
79
|
+
assertNoWindowsPathAlias(rootDirInput, "filesystem", "store root uses a Windows filesystem namespace alias");
|
|
80
|
+
const rootDir = resolvePathPreservingWindowsRoot(rootDirInput);
|
|
81
|
+
assertNoWindowsPathAlias(rootDir, "filesystem", "store root uses a Windows filesystem namespace alias");
|
|
102
82
|
const privateMode = options.private ?? false;
|
|
103
83
|
const dirMode = options.dirMode ?? 0o700;
|
|
104
84
|
const mode = options.mode ?? 0o600;
|
|
@@ -284,7 +264,10 @@ export function fileStore(options) {
|
|
|
284
264
|
};
|
|
285
265
|
}
|
|
286
266
|
export function fileStoreSync(options) {
|
|
287
|
-
const
|
|
267
|
+
const rootDirInput = options.rootDir;
|
|
268
|
+
assertNoWindowsPathAlias(rootDirInput, "filesystem", "store root uses a Windows filesystem namespace alias");
|
|
269
|
+
const rootDir = resolvePathPreservingWindowsRoot(rootDirInput);
|
|
270
|
+
assertNoWindowsPathAlias(rootDir, "filesystem", "store root uses a Windows filesystem namespace alias");
|
|
288
271
|
const privateMode = options.private ?? false;
|
|
289
272
|
const dirMode = options.dirMode ?? 0o700;
|
|
290
273
|
const mode = options.mode ?? 0o600;
|
package/dist/filename.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filename.d.ts","sourceRoot":"","sources":["../src/filename.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"filename.d.ts","sourceRoot":"","sources":["../src/filename.ts"],"names":[],"mappings":"AA0BA,wBAAgB,+BAA+B,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAexE;AA+CD,yFAAyF;AACzF,wBAAgB,8BAA8B,CAAC,MAAM,EAAE;IACrD,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,MAAM,CA8BT;AAiCD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAMxF"}
|