@openclaw/fs-safe 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +119 -0
- package/README.md +15 -8
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +32 -11
- package/dist/advanced.d.ts +3 -1
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +3 -1
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +4 -3
- package/dist/archive-gzip-tail.d.ts.map +1 -1
- package/dist/archive-gzip-tail.js +13 -9
- package/dist/archive-input.d.ts.map +1 -1
- package/dist/archive-input.js +6 -2
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +15 -2
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +7 -19
- package/dist/archive-plan.js +1 -1
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +55 -46
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +43 -10
- package/dist/archive-tar-inspect.d.ts.map +1 -1
- package/dist/archive-tar-inspect.js +4 -1
- package/dist/archive-zip-admission.d.ts +1 -1
- package/dist/archive-zip-admission.d.ts.map +1 -1
- package/dist/archive-zip-admission.js +2 -2
- package/dist/archive-zip-directory.d.ts +3 -0
- package/dist/archive-zip-directory.d.ts.map +1 -1
- package/dist/archive-zip-directory.js +13 -2
- package/dist/archive-zip-loader.d.ts +3 -2
- package/dist/archive-zip-loader.d.ts.map +1 -1
- package/dist/archive-zip-loader.js +30 -4
- package/dist/archive-zip-manifest.d.ts +5 -0
- package/dist/archive-zip-manifest.d.ts.map +1 -0
- package/dist/archive-zip-manifest.js +22 -0
- package/dist/archive-zip-names.d.ts +6 -1
- package/dist/archive-zip-names.d.ts.map +1 -1
- package/dist/archive-zip-names.js +50 -17
- package/dist/archive-zip-preflight.d.ts.map +1 -1
- package/dist/archive-zip-preflight.js +3 -2
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +9 -4
- package/dist/bounded-read.js +2 -2
- package/dist/darwin-acl.d.ts +4 -0
- package/dist/darwin-acl.d.ts.map +1 -0
- package/dist/darwin-acl.js +24 -0
- package/dist/deny-mutations.d.ts.map +1 -1
- package/dist/deny-mutations.js +8 -2
- package/dist/device-path.d.ts.map +1 -1
- package/dist/device-path.js +5 -3
- package/dist/directory-durability.d.ts.map +1 -1
- package/dist/directory-durability.js +67 -23
- package/dist/directory-entry-path.d.ts +3 -0
- package/dist/directory-entry-path.d.ts.map +1 -0
- package/dist/directory-entry-path.js +21 -0
- package/dist/directory-guard.d.ts +17 -1
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-guard.js +134 -48
- package/dist/directory-mode-node.d.ts +12 -0
- package/dist/directory-mode-node.d.ts.map +1 -1
- package/dist/directory-mode-node.js +102 -4
- package/dist/effective-uid.d.ts +2 -0
- package/dist/effective-uid.d.ts.map +1 -0
- package/dist/effective-uid.js +25 -0
- package/dist/file-handle-transfer.d.ts.map +1 -1
- package/dist/file-handle-transfer.js +98 -27
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +12 -2
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +36 -6
- package/dist/file-lock.d.ts.map +1 -1
- package/dist/file-lock.js +29 -9
- package/dist/file-observation.d.ts +1 -1
- package/dist/file-observation.d.ts.map +1 -1
- package/dist/file-store-boundary.d.ts +6 -2
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +20 -65
- package/dist/file-store-copy-source.d.ts +5 -0
- package/dist/file-store-copy-source.d.ts.map +1 -0
- package/dist/file-store-copy-source.js +31 -0
- package/dist/file-store-path.d.ts.map +1 -1
- package/dist/file-store-path.js +4 -1
- package/dist/file-store-prune.d.ts.map +1 -1
- package/dist/file-store-prune.js +9 -1
- package/dist/file-store-sync-directory.d.ts +16 -0
- package/dist/file-store-sync-directory.d.ts.map +1 -0
- package/dist/file-store-sync-directory.js +349 -0
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +11 -28
- package/dist/filename.d.ts +1 -0
- package/dist/filename.d.ts.map +1 -1
- package/dist/filename.js +85 -23
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +3 -2
- package/dist/guarded-mkdir.d.ts +10 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +217 -33
- package/dist/guest.d.ts.map +1 -1
- package/dist/guest.js +16 -5
- package/dist/home-dir.d.ts.map +1 -1
- package/dist/home-dir.js +73 -10
- package/dist/install-path.d.ts +6 -0
- package/dist/install-path.d.ts.map +1 -1
- package/dist/install-path.js +67 -19
- package/dist/json-durable-queue-ownership.d.ts +8 -0
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +134 -43
- package/dist/json-durable-queue-paths.d.ts +11 -0
- package/dist/json-durable-queue-paths.d.ts.map +1 -0
- package/dist/json-durable-queue-paths.js +42 -0
- package/dist/json-durable-queue-read.d.ts +6 -0
- package/dist/json-durable-queue-read.d.ts.map +1 -0
- package/dist/json-durable-queue-read.js +61 -0
- package/dist/json-durable-queue.d.ts +1 -1
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +83 -134
- package/dist/json-store.d.ts.map +1 -1
- package/dist/json-store.js +5 -1
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +54 -22
- package/dist/local-file-access.d.ts.map +1 -1
- package/dist/local-file-access.js +4 -0
- package/dist/local-file-descriptor.d.ts +17 -0
- package/dist/local-file-descriptor.d.ts.map +1 -0
- package/dist/local-file-descriptor.js +84 -0
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +35 -7
- package/dist/move-path-cleanup.d.ts +2 -0
- package/dist/move-path-cleanup.d.ts.map +1 -1
- package/dist/move-path-cleanup.js +44 -18
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +31 -6
- package/dist/native-binding.d.ts +21 -0
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-directory-observation.d.ts +17 -0
- package/dist/native-directory-observation.d.ts.map +1 -0
- package/dist/native-directory-observation.js +37 -0
- package/dist/native-parent-admission.d.ts +31 -0
- package/dist/native-parent-admission.d.ts.map +1 -0
- package/dist/native-parent-admission.js +124 -0
- package/dist/native-pinned-write-windows.d.ts +0 -1
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +0 -3
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +309 -40
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +15 -5
- package/dist/overwrite-file-handle.d.ts.map +1 -1
- package/dist/overwrite-file-handle.js +5 -1
- package/dist/owner-dacl.d.ts.map +1 -1
- package/dist/owner-dacl.js +2 -0
- package/dist/path-policy.d.ts.map +1 -1
- package/dist/path-policy.js +7 -2
- package/dist/path-prefix.d.ts +7 -0
- package/dist/path-prefix.d.ts.map +1 -0
- package/dist/path-prefix.js +82 -0
- package/dist/path-scope-lexical.d.ts +14 -0
- package/dist/path-scope-lexical.d.ts.map +1 -0
- package/dist/path-scope-lexical.js +37 -0
- package/dist/path-segment-route.d.ts +7 -0
- package/dist/path-segment-route.d.ts.map +1 -0
- package/dist/path-segment-route.js +24 -0
- package/dist/path-suffix-aliases.d.ts +10 -0
- package/dist/path-suffix-aliases.d.ts.map +1 -0
- package/dist/path-suffix-aliases.js +386 -0
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +35 -6
- package/dist/permissions-windows.d.ts.map +1 -1
- package/dist/permissions-windows.js +14 -3
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +37 -8
- package/dist/pinned-mutation-admission.d.ts +25 -0
- package/dist/pinned-mutation-admission.d.ts.map +1 -0
- package/dist/pinned-mutation-admission.js +425 -0
- package/dist/pinned-mutation-observation.d.ts +34 -0
- package/dist/pinned-mutation-observation.d.ts.map +1 -0
- package/dist/pinned-mutation-observation.js +142 -0
- package/dist/pinned-mutation-shared-route.d.ts +24 -0
- package/dist/pinned-mutation-shared-route.d.ts.map +1 -0
- package/dist/pinned-mutation-shared-route.js +70 -0
- package/dist/pinned-open.d.ts +6 -0
- package/dist/pinned-open.d.ts.map +1 -1
- package/dist/pinned-open.js +28 -10
- package/dist/pinned-write-types.d.ts +75 -0
- package/dist/pinned-write-types.d.ts.map +1 -0
- package/dist/pinned-write-types.js +1 -0
- package/dist/pinned-write.d.ts +7 -33
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +152 -17
- package/dist/private-directory.d.ts.map +1 -1
- package/dist/private-directory.js +2 -0
- package/dist/private-producer-handoff.d.ts +16 -0
- package/dist/private-producer-handoff.d.ts.map +1 -0
- package/dist/private-producer-handoff.js +272 -0
- package/dist/private-temp-workspace.d.ts +2 -39
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +183 -77
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +33 -29
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +56 -45
- package/dist/replace-directory.d.ts.map +1 -1
- package/dist/replace-directory.js +12 -5
- package/dist/replace-file-temp-owner.d.ts +1 -0
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +12 -1
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +24 -24
- package/dist/root-boundary.d.ts +29 -0
- package/dist/root-boundary.d.ts.map +1 -0
- package/dist/root-boundary.js +182 -0
- package/dist/root-context.d.ts +12 -1
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +89 -16
- package/dist/root-directory-creation.d.ts +13 -0
- package/dist/root-directory-creation.d.ts.map +1 -0
- package/dist/root-directory-creation.js +212 -0
- package/dist/root-directory-list.d.ts +16 -4
- package/dist/root-directory-list.d.ts.map +1 -1
- package/dist/root-directory-list.js +182 -33
- package/dist/root-directory.d.ts +23 -0
- package/dist/root-directory.d.ts.map +1 -0
- package/dist/root-directory.js +141 -0
- package/dist/root-errors.d.ts.map +1 -1
- package/dist/root-errors.js +12 -1
- package/dist/root-file-final-admission.d.ts +21 -0
- package/dist/root-file-final-admission.d.ts.map +1 -0
- package/dist/root-file-final-admission.js +83 -0
- package/dist/root-file.d.ts.map +1 -1
- package/dist/root-file.js +103 -24
- package/dist/root-impl.d.ts +1 -0
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +316 -325
- package/dist/root-move-noreplace.d.ts +14 -0
- package/dist/root-move-noreplace.d.ts.map +1 -0
- package/dist/root-move-noreplace.js +202 -0
- package/dist/root-move-preflight.d.ts +8 -0
- package/dist/root-move-preflight.d.ts.map +1 -0
- package/dist/root-move-preflight.js +16 -0
- package/dist/root-observed-path.d.ts +9 -0
- package/dist/root-observed-path.d.ts.map +1 -0
- package/dist/root-observed-path.js +95 -0
- package/dist/root-path-errors.d.ts +12 -0
- package/dist/root-path-errors.d.ts.map +1 -0
- package/dist/root-path-errors.js +13 -0
- package/dist/root-path-existing.d.ts +2 -0
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +63 -22
- package/dist/root-path-observation.d.ts +63 -0
- package/dist/root-path-observation.d.ts.map +1 -0
- package/dist/root-path-observation.js +180 -0
- package/dist/root-path-stat.d.ts +5 -0
- package/dist/root-path-stat.d.ts.map +1 -0
- package/dist/root-path-stat.js +101 -0
- package/dist/root-path-symlink.d.ts.map +1 -1
- package/dist/root-path-symlink.js +23 -4
- package/dist/root-path.d.ts +13 -0
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +263 -73
- package/dist/root-paths-lexical.d.ts +9 -0
- package/dist/root-paths-lexical.d.ts.map +1 -0
- package/dist/root-paths-lexical.js +22 -0
- package/dist/root-paths.d.ts +3 -29
- package/dist/root-paths.d.ts.map +1 -1
- package/dist/root-paths.js +89 -178
- package/dist/root-read-admission.d.ts +26 -0
- package/dist/root-read-admission.d.ts.map +1 -0
- package/dist/root-read-admission.js +94 -0
- package/dist/root-remove-identity.d.ts +15 -0
- package/dist/root-remove-identity.d.ts.map +1 -0
- package/dist/root-remove-identity.js +89 -0
- package/dist/root-remove-receipt.d.ts +17 -0
- package/dist/root-remove-receipt.d.ts.map +1 -0
- package/dist/root-remove-receipt.js +37 -0
- package/dist/root-remove.d.ts +2 -1
- package/dist/root-remove.d.ts.map +1 -1
- package/dist/root-remove.js +172 -10
- package/dist/root-walk.d.ts.map +1 -1
- package/dist/root-walk.js +2 -1
- package/dist/root-write-admission.d.ts +68 -0
- package/dist/root-write-admission.d.ts.map +1 -0
- package/dist/root-write-admission.js +322 -0
- package/dist/root-write-compatibility.d.ts +13 -0
- package/dist/root-write-compatibility.d.ts.map +1 -0
- package/dist/root-write-compatibility.js +74 -0
- package/dist/root-write-complete-parent.d.ts +42 -0
- package/dist/root-write-complete-parent.d.ts.map +1 -0
- package/dist/root-write-complete-parent.js +195 -0
- package/dist/root-write-mode.d.ts +2 -0
- package/dist/root-write-mode.d.ts.map +1 -1
- package/dist/root-write-mode.js +20 -7
- package/dist/root-write-publication.d.ts +24 -0
- package/dist/root-write-publication.d.ts.map +1 -0
- package/dist/root-write-publication.js +85 -0
- package/dist/root-write-verification.d.ts.map +1 -1
- package/dist/root-write-verification.js +18 -7
- package/dist/safe-path-segment.d.ts.map +1 -1
- package/dist/safe-path-segment.js +3 -1
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +62 -24
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +18 -13
- package/dist/secret-read-policy.d.ts.map +1 -1
- package/dist/secret-read-policy.js +5 -1
- package/dist/secure-file-windows.d.ts +10 -0
- package/dist/secure-file-windows.d.ts.map +1 -0
- package/dist/secure-file-windows.js +186 -0
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +98 -10
- package/dist/secure-temp-dir.d.ts +6 -3
- package/dist/secure-temp-dir.d.ts.map +1 -1
- package/dist/secure-temp-dir.js +121 -101
- package/dist/secure-temp-repair.d.ts +35 -0
- package/dist/secure-temp-repair.d.ts.map +1 -0
- package/dist/secure-temp-repair.js +106 -0
- package/dist/sibling-staged-file.d.ts +3 -1
- package/dist/sibling-staged-file.d.ts.map +1 -1
- package/dist/sibling-staged-file.js +181 -50
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +27 -12
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +45 -15
- package/dist/sidecar-lock-root.d.ts.map +1 -1
- package/dist/sidecar-lock-root.js +4 -2
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +2 -3
- package/dist/staged-directory.d.ts +14 -5
- package/dist/staged-directory.d.ts.map +1 -1
- package/dist/staged-directory.js +54 -3
- package/dist/standalone-publication-path.d.ts +2 -0
- package/dist/standalone-publication-path.d.ts.map +1 -0
- package/dist/standalone-publication-path.js +6 -0
- package/dist/stat-observation.d.ts +11 -0
- package/dist/stat-observation.d.ts.map +1 -0
- package/dist/stat-observation.js +64 -0
- package/dist/strict-file-identity.d.ts.map +1 -1
- package/dist/strict-file-identity.js +39 -2
- package/dist/symlink-parents.d.ts.map +1 -1
- package/dist/symlink-parents.js +7 -2
- package/dist/temp-target.d.ts +2 -0
- package/dist/temp-target.d.ts.map +1 -1
- package/dist/temp-target.js +133 -4
- package/dist/temp-workspace-admission.d.ts +22 -0
- package/dist/temp-workspace-admission.d.ts.map +1 -0
- package/dist/temp-workspace-admission.js +374 -0
- package/dist/temp-workspace-child-admission.d.ts +13 -0
- package/dist/temp-workspace-child-admission.d.ts.map +1 -0
- package/dist/temp-workspace-child-admission.js +95 -0
- package/dist/temp-workspace-descriptor.d.ts +48 -0
- package/dist/temp-workspace-descriptor.d.ts.map +1 -0
- package/dist/temp-workspace-descriptor.js +363 -0
- package/dist/temp-workspace-identity.d.ts +15 -0
- package/dist/temp-workspace-identity.d.ts.map +1 -0
- package/dist/temp-workspace-identity.js +41 -0
- package/dist/temp-workspace-owner.d.ts +7 -6
- package/dist/temp-workspace-owner.d.ts.map +1 -1
- package/dist/temp-workspace-owner.js +121 -61
- package/dist/temp-workspace-permissions.d.ts +4 -0
- package/dist/temp-workspace-permissions.d.ts.map +1 -0
- package/dist/temp-workspace-permissions.js +32 -0
- package/dist/temp-workspace-types.d.ts +40 -0
- package/dist/temp-workspace-types.d.ts.map +1 -0
- package/dist/temp-workspace-types.js +1 -0
- package/dist/temp.d.ts +1 -1
- package/dist/temp.d.ts.map +1 -1
- package/dist/temp.js +1 -1
- package/dist/test-hooks.d.ts +7 -0
- package/dist/test-hooks.d.ts.map +1 -1
- package/dist/text-atomic.d.ts.map +1 -1
- package/dist/text-atomic.js +3 -1
- package/dist/trash.d.ts.map +1 -1
- package/dist/trash.js +54 -8
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +20 -16
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +5 -0
- package/dist/windows-path-alias.d.ts +39 -0
- package/dist/windows-path-alias.d.ts.map +1 -0
- package/dist/windows-path-alias.js +153 -0
- package/docs/advanced.md +28 -1
- package/docs/archive.md +29 -2
- package/docs/atomic.md +27 -3
- package/docs/contributing.md +4 -1
- package/docs/copy.md +4 -1
- package/docs/durability.md +18 -2
- package/docs/errors.md +14 -6
- package/docs/file-store.md +30 -3
- package/docs/filename.md +21 -7
- package/docs/guest.md +5 -0
- package/docs/install-path.md +61 -15
- package/docs/install.md +12 -8
- package/docs/json-store.md +5 -1
- package/docs/json.md +11 -0
- package/docs/mutation-policy-proof.md +65 -0
- package/docs/native-helper.md +28 -9
- package/docs/native.md +46 -10
- package/docs/output.md +33 -11
- package/docs/path-prefix.md +64 -0
- package/docs/path-suffix-aliases.md +159 -0
- package/docs/path.md +12 -1
- package/docs/permissions.md +26 -1
- package/docs/private-file-store.md +14 -0
- package/docs/public-api.md +14 -0
- package/docs/quickstart.md +1 -1
- package/docs/reading.md +8 -1
- package/docs/root.md +21 -4
- package/docs/secret-file.md +3 -0
- package/docs/secure-file.md +21 -16
- package/docs/security-model.md +76 -9
- package/docs/sidecar-lock.md +28 -1
- package/docs/store.md +27 -2
- package/docs/temp.md +262 -34
- package/docs/test-hooks.md +14 -0
- package/docs/writing.md +38 -8
- package/package.json +10 -10
package/dist/walk.js
CHANGED
|
@@ -2,6 +2,7 @@ import fsSync from "node:fs";
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { realpathSync } from "./realpath.js";
|
|
5
|
+
import { pathForWindowsFilesystem, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
5
6
|
function validateWalkBudget(name, value) {
|
|
6
7
|
if (value !== undefined && (!Number.isSafeInteger(value) || value < 0)) {
|
|
7
8
|
throw new RangeError(`${name} must be a non-negative safe integer`);
|
|
@@ -29,11 +30,10 @@ function shouldStop(result, options) {
|
|
|
29
30
|
}
|
|
30
31
|
function buildEntry(params) {
|
|
31
32
|
const fullPath = params.fullPath;
|
|
32
|
-
const relativePath = path.relative(params.rootDir, fullPath) || params.dirent.name;
|
|
33
33
|
return {
|
|
34
34
|
name: params.dirent.name,
|
|
35
35
|
path: fullPath,
|
|
36
|
-
relativePath,
|
|
36
|
+
relativePath: params.relativePath,
|
|
37
37
|
depth: params.depth,
|
|
38
38
|
kind: params.kind ?? kindForDirent(params.dirent),
|
|
39
39
|
dirent: params.dirent,
|
|
@@ -70,7 +70,7 @@ function resolveKind(fullPath, dirent, symlinks) {
|
|
|
70
70
|
}
|
|
71
71
|
export function walkDirectorySync(rootDir, options = {}) {
|
|
72
72
|
validateWalkOptions(options);
|
|
73
|
-
const root =
|
|
73
|
+
const root = resolvePathPreservingWindowsRoot(rootDir);
|
|
74
74
|
const symlinks = options.symlinks ?? "skip";
|
|
75
75
|
const result = {
|
|
76
76
|
entries: [],
|
|
@@ -79,12 +79,13 @@ export function walkDirectorySync(rootDir, options = {}) {
|
|
|
79
79
|
failedDirs: [],
|
|
80
80
|
};
|
|
81
81
|
const visitedDirs = new Set();
|
|
82
|
-
function visit(dir, depth) {
|
|
82
|
+
function visit(dir, relativeDir, depth) {
|
|
83
83
|
if (options.maxDepth !== undefined && depth > options.maxDepth)
|
|
84
84
|
return;
|
|
85
85
|
let realDir;
|
|
86
|
+
const operationPath = pathForWindowsFilesystem(dir);
|
|
86
87
|
try {
|
|
87
|
-
realDir = realpathSync(
|
|
88
|
+
realDir = realpathSync(operationPath);
|
|
88
89
|
}
|
|
89
90
|
catch (error) {
|
|
90
91
|
recordFailedDir(result, root, dir, depth, error);
|
|
@@ -95,7 +96,7 @@ export function walkDirectorySync(rootDir, options = {}) {
|
|
|
95
96
|
visitedDirs.add(realDir);
|
|
96
97
|
let entries;
|
|
97
98
|
try {
|
|
98
|
-
entries = fsSync.readdirSync(
|
|
99
|
+
entries = fsSync.readdirSync(operationPath, { withFileTypes: true });
|
|
99
100
|
}
|
|
100
101
|
catch (error) {
|
|
101
102
|
recordFailedDir(result, root, dir, depth, error);
|
|
@@ -111,25 +112,26 @@ export function walkDirectorySync(rootDir, options = {}) {
|
|
|
111
112
|
const kind = resolveKind(fullPath, dirent, symlinks);
|
|
112
113
|
if (!kind)
|
|
113
114
|
continue;
|
|
114
|
-
const
|
|
115
|
+
const relativePath = relativeDir ? `${relativeDir}${path.sep}${dirent.name}` : dirent.name;
|
|
116
|
+
const entry = buildEntry({ relativePath, fullPath, dirent, depth, kind });
|
|
115
117
|
if (options.include?.(entry) ?? true) {
|
|
116
118
|
result.entries.push(entry);
|
|
117
119
|
}
|
|
118
120
|
if (kind === "directory" &&
|
|
119
121
|
(options.maxDepth === undefined || depth < options.maxDepth) &&
|
|
120
122
|
(options.descend?.(entry) ?? true)) {
|
|
121
|
-
visit(fullPath, depth + 1);
|
|
123
|
+
visit(fullPath, relativePath, depth + 1);
|
|
122
124
|
if (result.truncated)
|
|
123
125
|
return;
|
|
124
126
|
}
|
|
125
127
|
}
|
|
126
128
|
}
|
|
127
|
-
visit(root, 1);
|
|
129
|
+
visit(root, "", 1);
|
|
128
130
|
return result;
|
|
129
131
|
}
|
|
130
132
|
export async function walkDirectory(rootDir, options = {}) {
|
|
131
133
|
validateWalkOptions(options);
|
|
132
|
-
const root =
|
|
134
|
+
const root = resolvePathPreservingWindowsRoot(rootDir);
|
|
133
135
|
const symlinks = options.symlinks ?? "skip";
|
|
134
136
|
const result = {
|
|
135
137
|
entries: [],
|
|
@@ -138,12 +140,13 @@ export async function walkDirectory(rootDir, options = {}) {
|
|
|
138
140
|
failedDirs: [],
|
|
139
141
|
};
|
|
140
142
|
const visitedDirs = new Set();
|
|
141
|
-
async function visit(dir, depth) {
|
|
143
|
+
async function visit(dir, relativeDir, depth) {
|
|
142
144
|
if (options.maxDepth !== undefined && depth > options.maxDepth)
|
|
143
145
|
return;
|
|
144
146
|
let realDir;
|
|
147
|
+
const operationPath = pathForWindowsFilesystem(dir);
|
|
145
148
|
try {
|
|
146
|
-
realDir = realpathSync.native(
|
|
149
|
+
realDir = realpathSync.native(operationPath);
|
|
147
150
|
}
|
|
148
151
|
catch (error) {
|
|
149
152
|
recordFailedDir(result, root, dir, depth, error);
|
|
@@ -154,7 +157,7 @@ export async function walkDirectory(rootDir, options = {}) {
|
|
|
154
157
|
visitedDirs.add(realDir);
|
|
155
158
|
let entries;
|
|
156
159
|
try {
|
|
157
|
-
entries = await fs.readdir(
|
|
160
|
+
entries = await fs.readdir(operationPath, { withFileTypes: true });
|
|
158
161
|
}
|
|
159
162
|
catch (error) {
|
|
160
163
|
recordFailedDir(result, root, dir, depth, error);
|
|
@@ -170,19 +173,20 @@ export async function walkDirectory(rootDir, options = {}) {
|
|
|
170
173
|
const kind = resolveKind(fullPath, dirent, symlinks);
|
|
171
174
|
if (!kind)
|
|
172
175
|
continue;
|
|
173
|
-
const
|
|
176
|
+
const relativePath = relativeDir ? `${relativeDir}${path.sep}${dirent.name}` : dirent.name;
|
|
177
|
+
const entry = buildEntry({ relativePath, fullPath, dirent, depth, kind });
|
|
174
178
|
if (options.include?.(entry) ?? true) {
|
|
175
179
|
result.entries.push(entry);
|
|
176
180
|
}
|
|
177
181
|
if (kind === "directory" &&
|
|
178
182
|
(options.maxDepth === undefined || depth < options.maxDepth) &&
|
|
179
183
|
(options.descend?.(entry) ?? true)) {
|
|
180
|
-
await visit(fullPath, depth + 1);
|
|
184
|
+
await visit(fullPath, relativePath, depth + 1);
|
|
181
185
|
if (result.truncated)
|
|
182
186
|
return;
|
|
183
187
|
}
|
|
184
188
|
}
|
|
185
189
|
}
|
|
186
|
-
await visit(root, 1);
|
|
190
|
+
await visit(root, "", 1);
|
|
187
191
|
return result;
|
|
188
192
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"windows-owner.d.ts","sourceRoot":"","sources":["../src/windows-owner.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,wBAAwB,EAC9B,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"windows-owner.d.ts","sourceRoot":"","sources":["../src/windows-owner.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,wBAAwB,EAC9B,MAAM,sBAAsB,CAAC;AAI9B,MAAM,MAAM,gBAAgB,GAAG,CAC7B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,KACX,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEjD,MAAM,MAAM,mBAAmB,GAAG;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,IAAI,CAAC,EAAE,eAAe,EAAE,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,wBAAwB,CAAC;IACvC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;AAqDF,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,IAAI,EAAE,gBAAgB,CAAC;CACxB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAmD/B"}
|
package/dist/windows-owner.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { formatPermissionErrorDetail, getPermissionCommandFailure, } from "./permission-exec.js";
|
|
2
2
|
import { resolveWindowsSystemCommand } from "./windows-command.js";
|
|
3
|
+
import { hasWindowsPathAlias } from "./windows-path-alias.js";
|
|
3
4
|
const SID_RE = /^\*?s-\d+-\d+(-\d+)+$/i;
|
|
4
5
|
const TRUSTED_OWNER_SIDS = new Set(["s-1-5-18", "s-1-5-32-544"]);
|
|
5
6
|
function normalizeSid(value) {
|
|
@@ -47,6 +48,10 @@ function parseWindowsAclFacts(parsed) {
|
|
|
47
48
|
return { daclPresent: parsed.daclPresent, aces };
|
|
48
49
|
}
|
|
49
50
|
export async function inspectWindowsOwner(params) {
|
|
51
|
+
if (hasWindowsPathAlias(params.targetPath, "filesystem", "win32")) {
|
|
52
|
+
const error = new Error("Path uses a Windows filesystem namespace alias");
|
|
53
|
+
return { error: String(error), errorCause: error };
|
|
54
|
+
}
|
|
50
55
|
let command = "";
|
|
51
56
|
let startedAt = performance.now();
|
|
52
57
|
try {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { FsSafeError } from "./errors.js";
|
|
2
|
+
export type WindowsPathAliasKind = "filesystem" | "relative";
|
|
3
|
+
/**
|
|
4
|
+
* Capture an ordinary Windows drive-relative path without normalizing its raw
|
|
5
|
+
* suffix. This is only for public APIs whose existing contract accepts such
|
|
6
|
+
* paths; callers must still run namespace-alias admission on the result.
|
|
7
|
+
*/
|
|
8
|
+
export declare function anchorWindowsDriveRelativePath(value: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Resolve a path without letting Node erase the separator from an exact
|
|
11
|
+
* extended-length drive root such as `\\?\C:\`. Bare `\\?\C:` input remains
|
|
12
|
+
* unchanged so the surrounding alias admission rejects it.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolvePathPreservingWindowsRoot(value: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Preserve a namespaced drive root after a caller has already resolved the
|
|
17
|
+
* input. This lets admission fast paths keep exactly one live path.resolve
|
|
18
|
+
* call while retaining the same root-repair behavior as the general helper.
|
|
19
|
+
*/
|
|
20
|
+
export declare function repairResolvedWindowsRoot(value: string, resolved: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* Resolve path segments against a base while preserving a namespaced drive
|
|
23
|
+
* root when Node normalizes a legitimate rooted input back to that root.
|
|
24
|
+
* Raw bare namespace drives stay bare so admission checks still reject them.
|
|
25
|
+
*/
|
|
26
|
+
export declare function resolvePathFromBasePreservingWindowsRoot(base: string, ...segments: string[]): string;
|
|
27
|
+
/**
|
|
28
|
+
* Adapt an admitted namespaced drive root for Node's Windows filesystem layer.
|
|
29
|
+
* Node removes the root separator from these paths during filesystem dispatch,
|
|
30
|
+
* so use the equivalent ordinary drive root for the operation. This is not an
|
|
31
|
+
* admission check: callers must validate attacker-controlled input first.
|
|
32
|
+
*/
|
|
33
|
+
export declare function pathForWindowsFilesystem(value: string): string;
|
|
34
|
+
/** Returns true when a Windows pathname can address an alternate filesystem namespace. */
|
|
35
|
+
export declare function hasWindowsPathAlias(value: string, kind: WindowsPathAliasKind, platform?: NodeJS.Platform | string): boolean;
|
|
36
|
+
export declare function assertNoWindowsPathAliasForPlatform(value: string, kind: WindowsPathAliasKind, message: string, platform: NodeJS.Platform | string | undefined): void;
|
|
37
|
+
export declare function assertNoWindowsPathAlias(value: string, kind?: WindowsPathAliasKind, message?: string, platform?: NodeJS.Platform | string): void;
|
|
38
|
+
export declare function isWindowsPathAliasError(error: unknown): error is FsSafeError;
|
|
39
|
+
//# sourceMappingURL=windows-path-alias.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windows-path-alias.d.ts","sourceRoot":"","sources":["../src/windows-path-alias.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,MAAM,oBAAoB,GAAG,YAAY,GAAG,UAAU,CAAC;AAsD7D;;;;GAIG;AACH,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAYpE;AAED;;;;GAIG;AACH,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAUtE;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAUjF;AAED;;;;GAIG;AACH,wBAAgB,wCAAwC,CACtD,IAAI,EAAE,MAAM,EACZ,GAAG,QAAQ,EAAE,MAAM,EAAE,GACpB,MAAM,CAgBR;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAkB9D;AAED,0FAA0F;AAC1F,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,oBAAoB,EAC1B,QAAQ,GAAE,MAAM,CAAC,QAAQ,GAAG,MAAyB,GACpD,OAAO,CAMT;AAED,wBAAgB,mCAAmC,CACjD,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,oBAAoB,EAC1B,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,CAAC,QAAQ,GAAG,MAAM,GAAG,SAAS,GAC7C,IAAI,CAMN;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,oBAAmC,EACzC,OAAO,SAAmD,EAC1D,QAAQ,GAAE,MAAM,CAAC,QAAQ,GAAG,MAAyB,GACpD,IAAI,CAMN;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAE5E"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { FsSafeError } from "./errors.js";
|
|
3
|
+
const COLON = 0x3a;
|
|
4
|
+
const FORWARD_SLASH = 0x2f;
|
|
5
|
+
const BACKSLASH = 0x5c;
|
|
6
|
+
const DOT = 0x2e;
|
|
7
|
+
const QUESTION_MARK = 0x3f;
|
|
8
|
+
function isAsciiLetter(code) {
|
|
9
|
+
return (code >= 0x41 && code <= 0x5a) || (code >= 0x61 && code <= 0x7a);
|
|
10
|
+
}
|
|
11
|
+
function isSeparator(code) {
|
|
12
|
+
return code === FORWARD_SLASH || code === BACKSLASH;
|
|
13
|
+
}
|
|
14
|
+
function rootedDriveColonIndex(value) {
|
|
15
|
+
if (value.length >= 3 &&
|
|
16
|
+
isAsciiLetter(value.charCodeAt(0)) &&
|
|
17
|
+
value.charCodeAt(1) === COLON &&
|
|
18
|
+
isSeparator(value.charCodeAt(2))) {
|
|
19
|
+
return 1;
|
|
20
|
+
}
|
|
21
|
+
if (value.length >= 7 &&
|
|
22
|
+
isSeparator(value.charCodeAt(0)) &&
|
|
23
|
+
isSeparator(value.charCodeAt(1)) &&
|
|
24
|
+
(value.charCodeAt(2) === QUESTION_MARK || value.charCodeAt(2) === DOT) &&
|
|
25
|
+
isSeparator(value.charCodeAt(3)) &&
|
|
26
|
+
isAsciiLetter(value.charCodeAt(4)) &&
|
|
27
|
+
value.charCodeAt(5) === COLON &&
|
|
28
|
+
isSeparator(value.charCodeAt(6))) {
|
|
29
|
+
return 5;
|
|
30
|
+
}
|
|
31
|
+
return -1;
|
|
32
|
+
}
|
|
33
|
+
function isBareWindowsNamespaceDrive(value) {
|
|
34
|
+
return (value.length === 6 &&
|
|
35
|
+
isSeparator(value.charCodeAt(0)) &&
|
|
36
|
+
isSeparator(value.charCodeAt(1)) &&
|
|
37
|
+
(value.charCodeAt(2) === QUESTION_MARK || value.charCodeAt(2) === DOT) &&
|
|
38
|
+
isSeparator(value.charCodeAt(3)) &&
|
|
39
|
+
isAsciiLetter(value.charCodeAt(4)) &&
|
|
40
|
+
value.charCodeAt(5) === COLON);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Capture an ordinary Windows drive-relative path without normalizing its raw
|
|
44
|
+
* suffix. This is only for public APIs whose existing contract accepts such
|
|
45
|
+
* paths; callers must still run namespace-alias admission on the result.
|
|
46
|
+
*/
|
|
47
|
+
export function anchorWindowsDriveRelativePath(value) {
|
|
48
|
+
if (process.platform !== "win32" || path.isAbsolute(value))
|
|
49
|
+
return value;
|
|
50
|
+
if (value.length < 2 ||
|
|
51
|
+
!isAsciiLetter(value.charCodeAt(0)) ||
|
|
52
|
+
value.charCodeAt(1) !== COLON) {
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
const drive = value.slice(0, 2);
|
|
56
|
+
const base = path.resolve(drive);
|
|
57
|
+
return `${base}${path.sep}${value.slice(2)}`;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Resolve a path without letting Node erase the separator from an exact
|
|
61
|
+
* extended-length drive root such as `\\?\C:\`. Bare `\\?\C:` input remains
|
|
62
|
+
* unchanged so the surrounding alias admission rejects it.
|
|
63
|
+
*/
|
|
64
|
+
export function resolvePathPreservingWindowsRoot(value) {
|
|
65
|
+
if (value.length === 7 &&
|
|
66
|
+
process.platform === "win32" &&
|
|
67
|
+
rootedDriveColonIndex(value) === 5) {
|
|
68
|
+
return value.includes("/") ? value.replaceAll("/", "\\") : value;
|
|
69
|
+
}
|
|
70
|
+
const resolved = path.resolve(value);
|
|
71
|
+
return repairResolvedWindowsRoot(value, resolved);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Preserve a namespaced drive root after a caller has already resolved the
|
|
75
|
+
* input. This lets admission fast paths keep exactly one live path.resolve
|
|
76
|
+
* call while retaining the same root-repair behavior as the general helper.
|
|
77
|
+
*/
|
|
78
|
+
export function repairResolvedWindowsRoot(value, resolved) {
|
|
79
|
+
if (resolved.length === 6 &&
|
|
80
|
+
process.platform === "win32" &&
|
|
81
|
+
isBareWindowsNamespaceDrive(resolved) &&
|
|
82
|
+
!hasWindowsPathAlias(value, "filesystem")) {
|
|
83
|
+
return `${resolved}\\`;
|
|
84
|
+
}
|
|
85
|
+
return resolved;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Resolve path segments against a base while preserving a namespaced drive
|
|
89
|
+
* root when Node normalizes a legitimate rooted input back to that root.
|
|
90
|
+
* Raw bare namespace drives stay bare so admission checks still reject them.
|
|
91
|
+
*/
|
|
92
|
+
export function resolvePathFromBasePreservingWindowsRoot(base, ...segments) {
|
|
93
|
+
const resolved = path.resolve(base, ...segments);
|
|
94
|
+
if (resolved.length !== 6 ||
|
|
95
|
+
process.platform !== "win32" ||
|
|
96
|
+
!isBareWindowsNamespaceDrive(resolved)) {
|
|
97
|
+
return resolved;
|
|
98
|
+
}
|
|
99
|
+
if (hasWindowsPathAlias(base, "filesystem") ||
|
|
100
|
+
segments.some((segment) => hasWindowsPathAlias(segment, "filesystem"))) {
|
|
101
|
+
return resolved;
|
|
102
|
+
}
|
|
103
|
+
return `${resolved}\\`;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Adapt an admitted namespaced drive root for Node's Windows filesystem layer.
|
|
107
|
+
* Node removes the root separator from these paths during filesystem dispatch,
|
|
108
|
+
* so use the equivalent ordinary drive root for the operation. This is not an
|
|
109
|
+
* admission check: callers must validate attacker-controlled input first.
|
|
110
|
+
*/
|
|
111
|
+
export function pathForWindowsFilesystem(value) {
|
|
112
|
+
if (process.platform !== "win32" ||
|
|
113
|
+
rootedDriveColonIndex(value) !== 5) {
|
|
114
|
+
return value;
|
|
115
|
+
}
|
|
116
|
+
if (value.length === 7) {
|
|
117
|
+
return `${value[4]}:\\`;
|
|
118
|
+
}
|
|
119
|
+
const resolved = path.resolve(value);
|
|
120
|
+
if (isBareWindowsNamespaceDrive(resolved) &&
|
|
121
|
+
!hasWindowsPathAlias(value, "filesystem")) {
|
|
122
|
+
return `${resolved[4]}:\\`;
|
|
123
|
+
}
|
|
124
|
+
return value;
|
|
125
|
+
}
|
|
126
|
+
/** Returns true when a Windows pathname can address an alternate filesystem namespace. */
|
|
127
|
+
export function hasWindowsPathAlias(value, kind, platform = process.platform) {
|
|
128
|
+
if (platform !== "win32")
|
|
129
|
+
return false;
|
|
130
|
+
const firstColon = value.indexOf(":");
|
|
131
|
+
if (firstColon === -1)
|
|
132
|
+
return false;
|
|
133
|
+
if (kind === "relative")
|
|
134
|
+
return true;
|
|
135
|
+
return firstColon !== rootedDriveColonIndex(value) || value.indexOf(":", firstColon + 1) !== -1;
|
|
136
|
+
}
|
|
137
|
+
export function assertNoWindowsPathAliasForPlatform(value, kind, message, platform) {
|
|
138
|
+
if (hasWindowsPathAlias(value, kind, platform)) {
|
|
139
|
+
throw new FsSafeError("invalid-path", message, {
|
|
140
|
+
details: { reason: "windows-path-alias" },
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
export function assertNoWindowsPathAlias(value, kind = "filesystem", message = "path uses a Windows filesystem namespace alias", platform = process.platform) {
|
|
145
|
+
if (hasWindowsPathAlias(value, kind, platform)) {
|
|
146
|
+
throw new FsSafeError("invalid-path", message, {
|
|
147
|
+
details: { reason: "windows-path-alias" },
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
export function isWindowsPathAliasError(error) {
|
|
152
|
+
return error instanceof FsSafeError && error.details?.reason === "windows-path-alias";
|
|
153
|
+
}
|
package/docs/advanced.md
CHANGED
|
@@ -32,7 +32,9 @@ The exports group into a handful of themes. Each documented helper has its own p
|
|
|
32
32
|
| `resolveWritablePathWithinRoot` | – | Resolve a write target inside a root. |
|
|
33
33
|
| `resolveRootPath`, `resolveRootPathSync`, `ResolvedRootPath`, `ROOT_PATH_ALIAS_POLICIES`, `RootPathAliasPolicy` | – | Resolve a root directory honoring alias policy. |
|
|
34
34
|
| `resolvePathViaExistingAncestorSync` | – | Walk to an existing ancestor for paths whose tail does not yet exist. |
|
|
35
|
+
| `resolvePathPrefixSync`, `ResolvedPathPrefix` | [path-prefix.md](path-prefix.md) | Follow physical symlink targets and return a canonical existing prefix with the raw missing suffix; propagate uncertain resolution failures. |
|
|
35
36
|
| `probePathCaseInsensitiveSync`, `ProbePathCaseOptions` | [path-case.md](path-case.md) | Observe local ASCII-case behavior with explicit read-only mode and owned temporary-probe cleanup. |
|
|
37
|
+
| `probePathSuffixAliasesSync`, `ProbePathSuffixAliasesOptions` | [path-suffix-aliases.md](path-suffix-aliases.md) | Observe selected missing suffix aliases with bounded temporary directory probes; ambiguity, dynamic budget exhaustion, or incomplete cleanup returns `undefined`. |
|
|
36
38
|
|
|
37
39
|
`ensureDirectoryWithinRoot({ rootDir, requestedPath, scopeLabel, defaultDirName?, mode? })`
|
|
38
40
|
returns `{ ok: true, path }` or `{ ok: false, error: string, diagnostic?: FsSafeError }`.
|
|
@@ -77,6 +79,11 @@ Operational filesystem failures such as permissions or I/O errors are rethrown.
|
|
|
77
79
|
| `assertNoSymlinkParents`, `assertNoSymlinkParentsSync`, `AssertNoSymlinkParentsOptions` | – | Reject paths whose ancestor chain contains symlinks. |
|
|
78
80
|
| `assertNoHardlinkedFinalPath`, `assertNoPathAliasEscape`, `PATH_ALIAS_POLICIES`, `PathAliasPolicy` | – | Hardlink/alias defense building blocks. |
|
|
79
81
|
|
|
82
|
+
`pathExists()` and `pathExistsSync()` intentionally retain ordinary `stat`
|
|
83
|
+
semantics and are not caller-path admission boundaries. Validate an untrusted
|
|
84
|
+
path with the boundary appropriate to the operation before using these
|
|
85
|
+
existence probes.
|
|
86
|
+
|
|
80
87
|
`openRootFile()` and `openRootFileSync()` compare exact bigint identities before
|
|
81
88
|
open, on the retained descriptor, and on the current resolved path. Their `stat`
|
|
82
89
|
receipts remain numeric. Custom `ioFs` adapters must honor `{ bigint: true }` for
|
|
@@ -84,6 +91,24 @@ receipts remain numeric. Custom `ioFs` adapters must honor `{ bigint: true }` fo
|
|
|
84
91
|
Windows identities receive one re-inspection without reopening, then fail
|
|
85
92
|
validation if still unknown.
|
|
86
93
|
|
|
94
|
+
On Windows, these existing-object readers retain their historical support for a
|
|
95
|
+
leading drive-relative spelling such as `C:existing.txt`: it is anchored to that
|
|
96
|
+
drive before confinement and namespace admission. Additional colons remain in
|
|
97
|
+
the anchored spelling, so alternate-stream and directory-index aliases are still
|
|
98
|
+
rejected before opening.
|
|
99
|
+
|
|
100
|
+
These adapters also capture the canonical root directory's exact bigint identity
|
|
101
|
+
before component traversal. Immediately before transferring descriptor ownership,
|
|
102
|
+
they check that root, freshly canonicalize the consumed pathname, admit the fresh
|
|
103
|
+
spelling under the captured root, compare a no-follow canonical-leaf observation
|
|
104
|
+
with the retained descriptor, and check the root again. Boundary or identity drift
|
|
105
|
+
is a validation failure and the descriptor is closed. A custom `ioFs` supplies
|
|
106
|
+
these observations; the built-in adapter uses fs-safe's native realpath wrapper.
|
|
107
|
+
On Windows, the built-in adapter binds native root spelling before traversal,
|
|
108
|
+
including supplied `rootRealPath`, and returns that spelling in its root receipt.
|
|
109
|
+
This is an operation-local detection fence, not atomic confinement against a peer
|
|
110
|
+
that can keep racing pathname bindings.
|
|
111
|
+
|
|
87
112
|
The explicit `symlinks` policy takes precedence over the existing `rejectSymlinks`
|
|
88
113
|
boolean. Without `symlinks`, `rejectSymlinks: false` retains its existing behavior
|
|
89
114
|
of following contained links, and omission still rejects all symlink components.
|
|
@@ -96,6 +121,8 @@ byte limit. Continuation buffers grow only after filling with actual bytes,
|
|
|
96
121
|
up to the byte budget plus its probe; file-size hints cannot force that growth.
|
|
97
122
|
Unknown-size inputs start with at most 64 KiB. This avoids per-chunk copies and
|
|
98
123
|
a final concatenation when a file exceeds the initial allocation.
|
|
124
|
+
Regular files that report a size of zero, such as virtual files, continue through
|
|
125
|
+
positive short reads until actual EOF or byte-limit overflow.
|
|
99
126
|
|
|
100
127
|
The bounded descriptor helpers start at the descriptor's current offset and
|
|
101
128
|
leave ownership with the caller. They are intended for the second half of a
|
|
@@ -133,7 +160,7 @@ component is followed by another segment, both helpers throw
|
|
|
133
160
|
|
|
134
161
|
| Export | Page | Notes |
|
|
135
162
|
|---|---|---|
|
|
136
|
-
| `safeDirName`, `safePathSegmentHashed`, `resolveSafeInstallDir`, `assertCanonicalPathWithinBase` | [install-path.md](install-path.md) | Build install-target directories
|
|
163
|
+
| `safeDirName`, `safePathSegmentHashed`, `safePathSegmentHashedV2`, `resolveSafeInstallDir`, `assertCanonicalPathWithinBase` | [install-path.md](install-path.md) | Build install-target directories; use V2 for untrusted identifier mappings. |
|
|
137
164
|
| `sanitizeUntrustedFileName` | [filename.md](filename.md) | Coerce an untrusted string into a safe filename. |
|
|
138
165
|
| `resolveHomeRelativePath` | – | Expand a leading `~` before resolving `.` and `..`; tildes inside relative paths stay literal. |
|
|
139
166
|
|
package/docs/archive.md
CHANGED
|
@@ -139,7 +139,29 @@ accepted-entry plan back to Rust. Rust owns raw-stream admission, decompression,
|
|
|
139
139
|
and fd-relative `mkdirBeneath`/exclusive-open writes. This keeps filter policy identical
|
|
140
140
|
between native and JavaScript paths rather than reimplementing it in Rust.
|
|
141
141
|
|
|
142
|
-
ZIP extraction and bounded reads admit every physical central-directory record and its referenced local header before either decoder can normalize or collapse names. Raw names and valid Unicode Path names must pass traversal checks before stripping, filtering, or selecting a requested member; duplicate or colliding names reject with `entry-path`, even in unrelated or skipped members. Materially conflicting local/central or Unicode interpretations, malformed critical metadata, and ambiguous framing reject with `ArchiveFormatError`. Harmless separator and dot-component equivalence is allowed only after validation
|
|
142
|
+
ZIP extraction and bounded reads admit every physical central-directory record and its referenced local header before either decoder can normalize or collapse names. Raw names and valid Unicode Path names must pass traversal checks before stripping, filtering, or selecting a requested member; duplicate or colliding names reject with `entry-path`, even in unrelated or skipped members. Materially conflicting local/central or Unicode interpretations, malformed critical metadata, and ambiguous framing reject with `ArchiveFormatError`. Harmless internal separator and dot-component equivalence is allowed only after validation; every raw and Unicode interpretation must also agree on whether its name ends in `/` or `\`. Ordinary legacy filename decoding remains backend-selected. Native ZIP extraction groups nearby metadata reads into at most two 4 KiB read-ahead buffers per admission pass; larger records retain separately bounded reads. Buffered record views remain stable across eviction, and cached work periodically yields for deadline checks.
|
|
143
|
+
|
|
144
|
+
ZIP admission establishes each entry's kind before callbacks: a high-word UNIX
|
|
145
|
+
symlink type takes precedence regardless of creator, followed by the DOS directory
|
|
146
|
+
bit, the exact UNIX directory type, or a terminal slash/backslash. Native manifests
|
|
147
|
+
must agree with this kind, physical index, size, known path, and UNIX-creator mode
|
|
148
|
+
before extraction or any member read. Bounded ZIP reads retain this metadata from
|
|
149
|
+
their single admission pass without another input copy or scan.
|
|
150
|
+
|
|
151
|
+
Portable ZIP preflight, extraction, and reads also check the decoded kind against
|
|
152
|
+
admission. Unsupported JSZip metadata rejects with `ArchiveFormatError` before
|
|
153
|
+
filters, including UNIX-only directory attributes without a terminal slash or DOS
|
|
154
|
+
directory bit, backslash-only directory names without directory attributes, and
|
|
155
|
+
non-UNIX creators whose high-word symlink mode JSZip does not expose. Symlinks that
|
|
156
|
+
the decoder represents faithfully remain subject to the existing filter and
|
|
157
|
+
blocked-link policy. UNIX creator metadata and permission defaults remain as
|
|
158
|
+
described above.
|
|
159
|
+
|
|
160
|
+
Within one ZIP entry, identical local and central name bytes reuse the same
|
|
161
|
+
decoded validation. Unicode Path admission is shared only when both the raw names
|
|
162
|
+
and the complete Unicode fields match; different fields still verify their own
|
|
163
|
+
CRC and interpretation. Shared backing memory is checked independently. Decoded
|
|
164
|
+
name validation is not reused across entries or archives.
|
|
143
165
|
|
|
144
166
|
`stripComponents` removes leading nonempty, non-`.` path components after
|
|
145
167
|
normalizing separators. For example, `./pkg/hello.txt` with
|
|
@@ -226,7 +248,7 @@ omitted and do not consume output payload budgets. The shared core admits these
|
|
|
226
248
|
record and are then cleared; local PAX on unsupported types and GNU sparse
|
|
227
249
|
`S` records retain their existing fail-closed format policy.
|
|
228
250
|
|
|
229
|
-
If `kind` is omitted, the helper calls `resolveArchiveKind(archivePath)` and throws if the extension is not recognized. Pass `kind` explicitly when the archive name doesn't carry the type (e.g. content-addressed names). Archive inputs must remain regular files from preview through descriptor admission; POSIX opens are no-follow and nonblocking, so a FIFO swap cannot stall before deadline checks resume. A positive finite `timeoutMs` is a wall-clock budget; zero, negative, `NaN`, and infinity disable the deadline. Non-mutating work rejects promptly when the budget expires. If a live destination mutation is already in flight, rejection waits only for that mutation and any rollback to finish; no later destination mutation can begin.
|
|
251
|
+
If `kind` is omitted, the helper calls `resolveArchiveKind(archivePath)` and throws if the extension is not recognized. Pass `kind` explicitly when the archive name doesn't carry the type (e.g. content-addressed names). Archive inputs must remain regular files from preview through descriptor admission; POSIX opens are no-follow and nonblocking, so a FIFO swap cannot stall before deadline checks resume. On Windows, archive source and destination filesystem paths reject NTFS alternate-stream and directory-index namespace spellings such as `file:stream` and `dir::$INDEX_ALLOCATION`; ordinary colon-bearing POSIX names remain valid. A positive finite `timeoutMs` is a wall-clock budget; zero, negative, `NaN`, and infinity disable the deadline. Non-mutating work rejects promptly when the budget expires. If a live destination mutation is already in flight, rejection waits only for that mutation and any rollback to finish; no later destination mutation can begin.
|
|
230
252
|
|
|
231
253
|
Extraction captures the destination's lossless filesystem identity before any
|
|
232
254
|
entry filter runs and retains that capability through final publication. If a
|
|
@@ -315,6 +337,11 @@ codes remain `"destination-not-directory"`, `"destination-symlink"`, and
|
|
|
315
337
|
- **Slow-loris archives:** `timeoutMs` is a hard wall-clock budget for non-mutating work. Extraction is aborted on overrun; if a destination mutation is already in flight, that mutation and rollback are joined before rejection so archive-controlled publication cannot continue afterward.
|
|
316
338
|
- **Metadata bombs:** a streaming pass-through reader rejects oversized PAX, GNU long-name, and GNU long-link bodies before buffering their bodies. It understands octal and base-256 fixed sizes and validates bounded local PAX bodies before using their size overrides for member framing. Original archive bytes remain unchanged.
|
|
317
339
|
|
|
340
|
+
Native gzip, zstd, and bzip2 readers check cancellation before refilling
|
|
341
|
+
compressed input and before each decoded read, including buffered output. These checks
|
|
342
|
+
apply to file extraction and in-memory member reads; they cannot interrupt an
|
|
343
|
+
already-running filesystem read or a decoder step using already-buffered input.
|
|
344
|
+
|
|
318
345
|
### Raw TAR framing
|
|
319
346
|
|
|
320
347
|
Extraction and bounded reads admit the complete decoded TAR stream through the
|
package/docs/atomic.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Atomic writes
|
|
2
2
|
|
|
3
|
-
`@openclaw/fs-safe/atomic` re-exports the lower-level helpers that `root()`'s write methods are built on. Reach for them when you have
|
|
3
|
+
`@openclaw/fs-safe/atomic` re-exports the lower-level helpers that `root()`'s write methods are built on. Reach for them when you have a path you trust and want sibling-temp + rename without setting up a `Root`, or when you need finer control over `fsync`, mode preservation, or pre-rename hooks.
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
6
|
import {
|
|
@@ -20,6 +20,14 @@ On POSIX, the parent is opened with no-follow and directory-only flags, checked
|
|
|
20
20
|
|
|
21
21
|
Async replacements to the same destination are serialized inside the current process, so two overlapping `replaceFileAtomic()` calls do not interleave their temp-write/rename phases. Use a sidecar lock when multiple processes may write the same target.
|
|
22
22
|
|
|
23
|
+
Relative paths retain their literal suffix so `.` and `..` keep Node's existing
|
|
24
|
+
filesystem semantics. On Windows, an ordinary drive-relative destination such
|
|
25
|
+
as `C:.\\state.json` is captured at entry by anchoring the drive's current
|
|
26
|
+
directory without normalizing that suffix. The anchored absolute spelling is
|
|
27
|
+
used for staging, locks, callbacks, publication, and cleanup. Any additional
|
|
28
|
+
colon remains visible and is rejected as a filesystem namespace alias before
|
|
29
|
+
I/O; malformed namespace-drive spellings remain rejected.
|
|
30
|
+
|
|
23
31
|
```ts
|
|
24
32
|
import { replaceFileAtomic } from "@openclaw/fs-safe/atomic";
|
|
25
33
|
|
|
@@ -174,6 +182,8 @@ await replaceDirectoryAtomic({
|
|
|
174
182
|
The helper renames `targetDir` to a generated backup path, renames `stagedDir → targetDir`, then removes the backup. If the second rename fails, it tries to restore the original target before rethrowing.
|
|
175
183
|
Concurrent replacements of the same resolved target are serialized inside the
|
|
176
184
|
current process so their backup, commit, and cleanup phases cannot interleave.
|
|
185
|
+
On Windows, ordinary drive-relative staged and target paths are anchored at
|
|
186
|
+
entry before namespace-alias admission and resolution.
|
|
177
187
|
`backupPrefix`, when supplied, is sanitized as one path prefix and cannot contain
|
|
178
188
|
path separators or NUL bytes; the generated backup tail is randomized.
|
|
179
189
|
|
|
@@ -248,6 +258,8 @@ await movePathWithCopyFallback({
|
|
|
248
258
|
```
|
|
249
259
|
|
|
250
260
|
Use it when source and destination might live on different filesystems (containers, tmpfs, separate volumes).
|
|
261
|
+
On Windows, ordinary drive-relative `from` and `to` paths are anchored at entry;
|
|
262
|
+
publication receipts report the resulting absolute destination.
|
|
251
263
|
The hardlink policy is captured when the move starts. Changing or reusing the
|
|
252
264
|
options object later does not change the policy of an in-flight move.
|
|
253
265
|
`sourceHardlinks: "reject"` performs a recursive preflight capped at 50,000
|
|
@@ -260,7 +272,19 @@ the preflight cap fails with `FsSafeError("too-large")`.
|
|
|
260
272
|
If another writer changes source entries during the fallback, the staged copy
|
|
261
273
|
throws `ESTALE` before commit when possible. If the destination has already
|
|
262
274
|
been committed, cleanup still preserves the changed source entries and throws
|
|
263
|
-
`ESTALE`.
|
|
275
|
+
`ESTALE`. Directory manifests retain an exact bigint device/inode receipt from
|
|
276
|
+
copy admission. Each directory is rechecked after traversal, and the source root
|
|
277
|
+
is checked again before publication. Cleanup checks the same receipt before
|
|
278
|
+
removing children, then invokes mutation authority and rechecks the receipt and
|
|
279
|
+
directory type immediately before removal. Unknown Windows identity
|
|
280
|
+
components get at most one retry that retains known components; persistent
|
|
281
|
+
ambiguity fails closed before further removal. A directory that disappeared or
|
|
282
|
+
was replaced during child cleanup is reported as stale; an observed replacement
|
|
283
|
+
is preserved. Unrelated children
|
|
284
|
+
added to the original directory are preserved while unchanged copied children
|
|
285
|
+
are still removed. These pathname checks remain best-effort: they cannot make
|
|
286
|
+
the final identity check and removal atomic against another process.
|
|
287
|
+
When allowed source names are hardlinks to the same inode, each owned
|
|
264
288
|
unlink is verified through a remaining manifested alias and its exact resulting
|
|
265
289
|
identity becomes the next cleanup receipt. This accounts for the operation's
|
|
266
290
|
own link-count and ctime changes without suppressing unexpected external
|
|
@@ -337,7 +361,7 @@ preserves the existing move and source-identity behavior.
|
|
|
337
361
|
|
|
338
362
|
| `Root` methods | `atomic` helpers |
|
|
339
363
|
|---|---|
|
|
340
|
-
| Take relative paths, bound to a `rootDir`. | Take absolute paths, no boundary. |
|
|
364
|
+
| Take relative paths, bound to a `rootDir`. | Take trusted absolute or relative paths, no boundary. |
|
|
341
365
|
| Throw `FsSafeError` with `code`. | Throw `FsSafeError` *or* the underlying `NodeJS.ErrnoException`, depending on failure point. |
|
|
342
366
|
| Atomicity, mode, hooks, fsync are sane defaults. | Caller controls all of the above. |
|
|
343
367
|
| `mkdir`, identity check, hardlink reject built in. | No root boundary; `movePathWithCopyFallback` has explicit `sourceHardlinks` policy, while other helpers expose their own narrower checks. |
|
package/docs/contributing.md
CHANGED
|
@@ -76,7 +76,10 @@ setup use `useSuiteFixture` from `test/helpers/suite-fixture.ts`: setup has a se
|
|
|
76
76
|
removing directories. Run shared-state corpora sequentially with a deadline per
|
|
77
77
|
payload. Keep child-process liveness limits separate from fixture preparation.
|
|
78
78
|
The Windows CI slow-copy proof runs the real package-copy process-exit test with a
|
|
79
|
-
|
|
79
|
+
16-second copy delay, a 10-second child deadline, a 15-second test deadline, and
|
|
80
|
+
60-second setup and teardown hook budgets. Other hosts retain the ordinary
|
|
81
|
+
six-second delay, four-second child deadline, five-second test deadline, and
|
|
82
|
+
30-second hook budgets:
|
|
80
83
|
|
|
81
84
|
```bash
|
|
82
85
|
pnpm build
|
package/docs/copy.md
CHANGED
|
@@ -64,7 +64,7 @@ Automatic copying does not recover from permission errors, I/O errors, cancellat
|
|
|
64
64
|
|
|
65
65
|
On Windows, automatic byte copying uses the native binding when available to transfer data between the already-checked file handles in chunks of at most 1 MiB, with smaller buffers for small files. This accelerates NTFS and cross-volume copies without reopening source or destination pathnames. The native worker finishes before its descriptors are closed or cancellation is reported. `clone: "never"` and native-disabled copies use JavaScript read/write loops with reusable buffers: at most 1 MiB per active file on Windows, or 128 KiB elsewhere. Both paths share the file-worker budget across sibling directories, wait for all admitted writes after cancellation or failure, and restore directory timestamps after their descendants finish. Completed directory traversals awaiting writes or metadata are bounded by concurrency; ancestors remain pinned while traversing their children.
|
|
66
66
|
|
|
67
|
-
On Linux, automatic byte copying also uses the native binding when available. It reads in 1 MiB
|
|
67
|
+
On Linux, automatic byte copying also uses the native binding when available. It reads in chunks up to 1 MiB, using smaller buffers for smaller source-size hints, and leaves leading and trailing zero-filled portions of each chunk unwritten in the new file, avoiding their allocation on filesystems that support sparse files. A final size update preserves trailing holes and all-zero files. This path uses reads and writes, without cloning or copy offload; it still reads the full logical contents and does not promise identical sparse extent layout. `clone: "never"` retains the JavaScript byte-copy path.
|
|
68
68
|
|
|
69
69
|
Clones preserve file contents, empty directories, timestamps, executable modes where supported, and literal symbolic links. Editing a clone does not modify its source. Unsupported filesystem operations fail; callers may choose their own copy or checkout fallback after the failed operation has settled.
|
|
70
70
|
|
|
@@ -97,6 +97,9 @@ const bytes = await copyFileHandle(sourceHandle, targetHandle, {
|
|
|
97
97
|
|
|
98
98
|
`CopyFileHandleOptions` contains optional `maxBytes`, `signal`, `onChunk`, and
|
|
99
99
|
`assertBeforeMutation`. The result is the actual byte count copied through EOF.
|
|
100
|
+
The four options are selected once before descriptor inspection; later mutation
|
|
101
|
+
of the options object cannot replace the active budget, signal, observer, or
|
|
102
|
+
mutation-authority callback.
|
|
100
103
|
The byte limit is not a prefix length: excess data rejects with `too-large`,
|
|
101
104
|
including data added after admission. Omitted limits are unlimited; Root's
|
|
102
105
|
default read cap does not apply. Zero accepts only an empty source. Invalid
|
package/docs/durability.md
CHANGED
|
@@ -53,6 +53,14 @@ propagate.
|
|
|
53
53
|
discard both unsupported outcomes and failures. Use them only when the primary
|
|
54
54
|
write remains useful without a crash-durability promise.
|
|
55
55
|
|
|
56
|
+
On Windows, pathname inputs and supplied directory receipts reject NTFS
|
|
57
|
+
alternate-stream and directory-index namespace spellings before opening,
|
|
58
|
+
creating, hashing, or publishing anything. This applies to directory
|
|
59
|
+
durability, `publishFileExclusive()`, and the pathname overloads of
|
|
60
|
+
`sha256File()` and `sha256FileSync()`; the already-open `FileHandle` and
|
|
61
|
+
borrowed numeric file-descriptor overloads are unchanged. Ordinary colon-bearing
|
|
62
|
+
POSIX paths remain valid.
|
|
63
|
+
|
|
56
64
|
## Pinned directories
|
|
57
65
|
|
|
58
66
|
`pinDirectory()` rejects final symlinks and non-directories. On POSIX it opens
|
|
@@ -94,6 +102,12 @@ target. It pins the source with nonblocking `O_NOFOLLOW`, optionally verifies
|
|
|
94
102
|
`expectedSourceIdentity`, tries a hardlink first, then synchronizes the target
|
|
95
103
|
parent directory.
|
|
96
104
|
|
|
105
|
+
Trusted relative source and target paths are resolved to absolute paths before
|
|
106
|
+
authority checks. On Windows, ordinary drive-relative operands are anchored at
|
|
107
|
+
entry before namespace-alias admission. A caller-supplied `parentReceipt` must
|
|
108
|
+
still name the resolved target parent and is not relaxed by this compatibility
|
|
109
|
+
rule.
|
|
110
|
+
|
|
97
111
|
For example, a backup archive is complete before publication. If directory
|
|
98
112
|
sync fails, keeping that complete file is more useful than conditionally
|
|
99
113
|
deleting it by pathname:
|
|
@@ -242,8 +256,10 @@ When the optional binding is active, hashing runs as an async native task and
|
|
|
242
256
|
does not occupy the JavaScript event loop with digest updates. With native mode
|
|
243
257
|
`off`, or in `auto` when no binding loads, the fallback performs asynchronous
|
|
244
258
|
positioned reads in chunks of up to 256 KiB but updates Node's `Hash` on the JavaScript
|
|
245
|
-
thread. Both paths stream
|
|
246
|
-
|
|
259
|
+
thread. Both paths stream bounded buffers rather than loading the file into memory.
|
|
260
|
+
The fallback sizes its scratch buffer to small files and grows it if a stale
|
|
261
|
+
size hint is exceeded, while still probing for actual EOF and byte-limit overflow.
|
|
262
|
+
Native mode `require` keeps its usual fail-closed loader semantics.
|
|
247
263
|
|
|
248
264
|
### Synchronous hashing
|
|
249
265
|
|