@openclaw/fs-safe 0.12.0 → 0.13.1
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 +73 -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/copy-file-input.d.ts +1 -1
- package/dist/copy-file-input.d.ts.map +1 -1
- package/dist/copy-file-input.js +2 -0
- 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 +17 -0
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-binding.js +8 -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-operations.d.ts.map +1 -1
- package/dist/native-operations.js +6 -4
- package/dist/native-parent-admission.d.ts +32 -0
- package/dist/native-parent-admission.d.ts.map +1 -0
- package/dist/native-parent-admission.js +127 -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 +8 -9
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +314 -42
- package/dist/native-staged-file.d.ts +4 -4
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +20 -10
- package/dist/native.d.ts +1 -1
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +7 -2
- 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 +38 -32
- 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 +41 -11
- 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
package/dist/filename.js
CHANGED
|
@@ -2,6 +2,8 @@ import path from "node:path";
|
|
|
2
2
|
import { WINDOWS_RESERVED_DEVICE_NAMES } from "./device-path.js";
|
|
3
3
|
import { maxNormalizedUtf8Bytes } from "./unicode-path.js";
|
|
4
4
|
const INVALID_FILE_NAME_CHARACTERS = /[\u0000-\u001f\u007f-\u009f<>:"/\\|?*]/g;
|
|
5
|
+
const HAS_INVALID_FILE_NAME_CHARACTER = /[\u0000-\u001f\u007f-\u009f<>:"/\\|?*]/;
|
|
6
|
+
const HAS_RETAINED_FILE_NAME_CHARACTER = /[^\u0000-\u001f\u007f-\u009f<>:"/\\|?*\s]/;
|
|
5
7
|
function canStartWindowsDeviceName(character) {
|
|
6
8
|
const folded = character | 0x20;
|
|
7
9
|
return folded === 0x61 || folded === 0x63 || folded === 0x6c ||
|
|
@@ -33,18 +35,44 @@ export function suffixWindowsReservedDeviceName(fileName) {
|
|
|
33
35
|
return `${fileName.slice(0, baseNameEnd)}_${fileName.slice(baseNameEnd)}`;
|
|
34
36
|
}
|
|
35
37
|
const PORTABLE_FILE_NAME_BYTES = 255;
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
const SANITIZED_FILE_NAME_CODE_UNITS = 200;
|
|
39
|
+
const SAFE_FALLBACK_FILE_NAME = "file";
|
|
40
|
+
/**
|
|
41
|
+
* Uses native string and regexp operations to recognize the full sanitizer's
|
|
42
|
+
* bounded fixed points. The separate non-global regexp keeps this predicate
|
|
43
|
+
* stateless, and the authoritative device-name transform remains the final
|
|
44
|
+
* admission check.
|
|
45
|
+
*/
|
|
46
|
+
function isBoundedSanitizedFileName(fileName, trimmedFileName) {
|
|
47
|
+
return typeof fileName === "string" &&
|
|
48
|
+
fileName.length > 0 &&
|
|
49
|
+
fileName.length <= SANITIZED_FILE_NAME_CODE_UNITS &&
|
|
50
|
+
fileName !== "." &&
|
|
51
|
+
fileName !== ".." &&
|
|
52
|
+
trimmedFileName === fileName &&
|
|
53
|
+
!HAS_INVALID_FILE_NAME_CHARACTER.test(fileName) &&
|
|
54
|
+
suffixWindowsReservedDeviceName(fileName) === fileName;
|
|
55
|
+
}
|
|
56
|
+
function hasWindowsDrivePrefix(value) {
|
|
57
|
+
if (value.length < 2 || value.charCodeAt(1) !== 0x3a)
|
|
58
|
+
return false;
|
|
59
|
+
const firstCodeUnit = value.charCodeAt(0);
|
|
60
|
+
return (firstCodeUnit >= 0x41 && firstCodeUnit <= 0x5a) ||
|
|
61
|
+
(firstCodeUnit >= 0x61 && firstCodeUnit <= 0x7a);
|
|
62
|
+
}
|
|
63
|
+
function normalizedFileNameBytes(value) {
|
|
64
|
+
return maxNormalizedUtf8Bytes(value, true);
|
|
65
|
+
}
|
|
66
|
+
function truncateCodeUnitsWithoutSplittingSurrogate(value, limit) {
|
|
67
|
+
if (value.length <= limit)
|
|
68
|
+
return value;
|
|
69
|
+
let truncated = value.slice(0, limit);
|
|
39
70
|
const trailingCodeUnit = truncated.charCodeAt(truncated.length - 1);
|
|
40
71
|
if (trailingCodeUnit >= 0xd800 && trailingCodeUnit <= 0xdbff) {
|
|
41
72
|
truncated = truncated.slice(0, -1);
|
|
42
73
|
}
|
|
43
74
|
return truncated;
|
|
44
75
|
}
|
|
45
|
-
function normalizedFileNameBytes(value) {
|
|
46
|
-
return maxNormalizedUtf8Bytes(value, true);
|
|
47
|
-
}
|
|
48
76
|
/** Keeps short names exact and trims only the filename tail of a composite temp name. */
|
|
49
77
|
export function fitFileNameToPortableComponent(params) {
|
|
50
78
|
const complete = `${params.prefix}${params.fileName}${params.suffix}`;
|
|
@@ -77,23 +105,40 @@ export function fitFileNameToPortableComponent(params) {
|
|
|
77
105
|
}
|
|
78
106
|
return `${codePoints.slice(0, low).join("")}${tailSuffix}`;
|
|
79
107
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
108
|
+
function sanitizeFileNameCandidate(fileName) {
|
|
109
|
+
if (typeof fileName !== "string")
|
|
110
|
+
return undefined;
|
|
111
|
+
const trimmed = fileName.trim();
|
|
112
|
+
if (!trimmed)
|
|
113
|
+
return undefined;
|
|
114
|
+
if (isBoundedSanitizedFileName(fileName, trimmed))
|
|
115
|
+
return fileName;
|
|
116
|
+
// Basename selection cannot manufacture a character, and the complete
|
|
117
|
+
// sanitizer removes this same invalid set before its final trim.
|
|
118
|
+
if (!HAS_RETAINED_FILE_NAME_CHARACTER.test(trimmed))
|
|
119
|
+
return undefined;
|
|
120
|
+
let base = trimmed;
|
|
121
|
+
if (base.includes("/"))
|
|
122
|
+
base = path.posix.basename(base);
|
|
123
|
+
if (base.includes("\\") || hasWindowsDrivePrefix(base)) {
|
|
124
|
+
base = path.win32.basename(base);
|
|
84
125
|
}
|
|
85
|
-
let base = path.posix.basename(trimmed);
|
|
86
|
-
base = path.win32.basename(base);
|
|
87
126
|
base = base.replace(INVALID_FILE_NAME_CHARACTERS, "").trim();
|
|
88
127
|
if (!base || base === "." || base === "..") {
|
|
89
|
-
return
|
|
128
|
+
return undefined;
|
|
90
129
|
}
|
|
91
|
-
base =
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
130
|
+
base = truncateCodeUnitsWithoutSplittingSurrogate(base, SANITIZED_FILE_NAME_CODE_UNITS);
|
|
131
|
+
let safeBase = suffixWindowsReservedDeviceName(base);
|
|
132
|
+
if (safeBase.length > SANITIZED_FILE_NAME_CODE_UNITS) {
|
|
133
|
+
// The safety suffix is the final invariant. Shorten the unsuffixed tail so
|
|
134
|
+
// truncation cannot turn a padded reserved stem back into a device name.
|
|
135
|
+
base = truncateCodeUnitsWithoutSplittingSurrogate(base, SANITIZED_FILE_NAME_CODE_UNITS - 1);
|
|
136
|
+
safeBase = suffixWindowsReservedDeviceName(base);
|
|
97
137
|
}
|
|
98
|
-
return
|
|
138
|
+
return safeBase;
|
|
139
|
+
}
|
|
140
|
+
export function sanitizeUntrustedFileName(fileName, fallbackName) {
|
|
141
|
+
return (sanitizeFileNameCandidate(fileName) ??
|
|
142
|
+
sanitizeFileNameCandidate(fallbackName) ??
|
|
143
|
+
SAFE_FALLBACK_FILE_NAME);
|
|
99
144
|
}
|
package/dist/fs.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOnE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAOxD"}
|
package/dist/fs.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import { pathForWindowsFilesystem } from "./windows-path-alias.js";
|
|
2
3
|
/**
|
|
3
4
|
* Returns true when `fs.stat()` can stat the path.
|
|
4
5
|
*
|
|
@@ -7,7 +8,7 @@ import fs from "node:fs";
|
|
|
7
8
|
*/
|
|
8
9
|
export async function pathExists(filePath) {
|
|
9
10
|
try {
|
|
10
|
-
fs.statSync(filePath);
|
|
11
|
+
fs.statSync(pathForWindowsFilesystem(filePath));
|
|
11
12
|
return true;
|
|
12
13
|
}
|
|
13
14
|
catch {
|
|
@@ -20,7 +21,7 @@ export async function pathExists(filePath) {
|
|
|
20
21
|
*/
|
|
21
22
|
export function pathExistsSync(filePath) {
|
|
22
23
|
try {
|
|
23
|
-
fs.statSync(filePath);
|
|
24
|
+
fs.statSync(pathForWindowsFilesystem(filePath));
|
|
24
25
|
return true;
|
|
25
26
|
}
|
|
26
27
|
catch {
|
package/dist/guarded-mkdir.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type RootBoundaryIdentity } from "./root-boundary.js";
|
|
2
|
+
import { type MutationDirectoryObservation } from "./pinned-mutation-observation.js";
|
|
3
|
+
import type { PinnedCreatedDirectoryReceipt, PinnedMutationAdmissionReceipt, PinnedMutationAuthorizationToken } from "./pinned-write.js";
|
|
2
4
|
/**
|
|
3
5
|
* Creates each missing path component from `rootReal` down to `targetPath`,
|
|
4
6
|
* guarding every step. Returns the real (symlink-resolved) path of the final
|
|
@@ -9,9 +11,15 @@ export declare function mkdirPathComponentsWithGuards(params: {
|
|
|
9
11
|
rootReal: string;
|
|
10
12
|
targetPath: string;
|
|
11
13
|
beforeComponent?: (componentPath: string) => Promise<void> | void;
|
|
14
|
+
beforeCreateComponent?: (componentPath: string, prospectiveTargetPath: string, retainedTargetPath: string | undefined, parent: MutationDirectoryObservation) => Promise<PinnedMutationAdmissionReceipt | undefined> | PinnedMutationAdmissionReceipt | undefined;
|
|
15
|
+
beforeUseComponent?: (componentPath: string, prospectiveTargetPath: string, retainedTargetPath: string | undefined) => Promise<void> | void;
|
|
16
|
+
afterCreateComponent?: (receipt: PinnedCreatedDirectoryReceipt) => PinnedMutationAuthorizationToken | undefined;
|
|
12
17
|
assertBeforeMutation?: () => void;
|
|
13
18
|
mode?: number;
|
|
14
19
|
rejectSymlinks?: boolean;
|
|
20
|
+
revalidateParentAfterBeforeComponent?: boolean;
|
|
21
|
+
synchronousAuthorizationIncludesFence?: boolean;
|
|
22
|
+
retainedTargetPath?: string;
|
|
15
23
|
rootIdentity?: RootBoundaryIdentity;
|
|
16
24
|
}): Promise<string>;
|
|
17
25
|
//# sourceMappingURL=guarded-mkdir.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guarded-mkdir.d.ts","sourceRoot":"","sources":["../src/guarded-mkdir.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"guarded-mkdir.d.ts","sourceRoot":"","sources":["../src/guarded-mkdir.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAuB,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AACpF,OAAO,EAA4B,KAAK,4BAA4B,EAAE,MAAM,kCAAkC,CAAC;AAC/G,OAAO,KAAK,EACV,6BAA6B,EAC7B,8BAA8B,EAC9B,gCAAgC,EACjC,MAAM,mBAAmB,CAAC;AAuG3B;;;;;GAKG;AACH,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAClE,qBAAqB,CAAC,EAAE,CACtB,aAAa,EAAE,MAAM,EACrB,qBAAqB,EAAE,MAAM,EAC7B,kBAAkB,EAAE,MAAM,GAAG,SAAS,EACtC,MAAM,EAAE,4BAA4B,KACjC,OAAO,CAAC,8BAA8B,GAAG,SAAS,CAAC,GAAG,8BAA8B,GAAG,SAAS,CAAC;IACtG,kBAAkB,CAAC,EAAE,CACnB,aAAa,EAAE,MAAM,EACrB,qBAAqB,EAAE,MAAM,EAC7B,kBAAkB,EAAE,MAAM,GAAG,SAAS,KACnC,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC1B,oBAAoB,CAAC,EAAE,CACrB,OAAO,EAAE,6BAA6B,KACnC,gCAAgC,GAAG,SAAS,CAAC;IAClD,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,oCAAoC,CAAC,EAAE,OAAO,CAAC;IAC/C,qCAAqC,CAAC,EAAE,OAAO,CAAC;IAChD,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,YAAY,CAAC,EAAE,oBAAoB,CAAC;CACrC,GAAG,OAAO,CAAC,MAAM,CAAC,CAyMlB"}
|
package/dist/guarded-mkdir.js
CHANGED
|
@@ -1,12 +1,58 @@
|
|
|
1
|
-
import fsSync from "node:fs";
|
|
1
|
+
import fsSync, {} from "node:fs";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard, } from "./directory-guard.js";
|
|
4
|
+
import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard, inspectDirectoryIdentitySync, } from "./directory-guard.js";
|
|
5
5
|
import { FsSafeError } from "./errors.js";
|
|
6
6
|
import { isNotFoundPathError, isPathRelativeEscape } from "./path.js";
|
|
7
7
|
import { directoryComponentNotDirectoryError, rootPathChangedError } from "./root-errors.js";
|
|
8
|
+
import { assertNoWindowsPathAlias, pathForWindowsFilesystem, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
8
9
|
import { realpathSync } from "./realpath.js";
|
|
9
10
|
import { admitPathInsideRoot } from "./root-boundary.js";
|
|
11
|
+
import { checkedMutationDirectory } from "./pinned-mutation-observation.js";
|
|
12
|
+
import { createPathSegmentRoute, joinPathSegmentRoute, } from "./path-segment-route.js";
|
|
13
|
+
function sameDirectoryFacts(left, right) {
|
|
14
|
+
return left.dev === right.dev && left.ino === right.ino && left.mode === right.mode &&
|
|
15
|
+
left.nlink === right.nlink;
|
|
16
|
+
}
|
|
17
|
+
function inspectGuardCurrent(parent) {
|
|
18
|
+
const stat = inspectDirectoryIdentitySync(parent.dir, parent.stat);
|
|
19
|
+
if (realpathSync.native(parent.dir) !== parent.realPath) {
|
|
20
|
+
throw new FsSafeError("path-mismatch", "directory changed during operation");
|
|
21
|
+
}
|
|
22
|
+
return stat;
|
|
23
|
+
}
|
|
24
|
+
function observedGuard(parent) {
|
|
25
|
+
return checkedMutationDirectory(parent.dir, parent.realPath, parent.stat);
|
|
26
|
+
}
|
|
27
|
+
function createdDirectoryEvidence(admission, parent, childPath) {
|
|
28
|
+
try {
|
|
29
|
+
const parentAfter = inspectGuardCurrent(parent);
|
|
30
|
+
const childBefore = inspectDirectoryIdentitySync(childPath);
|
|
31
|
+
const realPath = realpathSync.native(childPath);
|
|
32
|
+
if (realPath !== childPath)
|
|
33
|
+
return undefined;
|
|
34
|
+
const childAfter = inspectDirectoryIdentitySync(childPath, childBefore);
|
|
35
|
+
if (!sameDirectoryFacts(childBefore, childAfter))
|
|
36
|
+
return undefined;
|
|
37
|
+
const child = checkedMutationDirectory(childPath, realPath, childAfter);
|
|
38
|
+
const receipt = Object.freeze({
|
|
39
|
+
admission,
|
|
40
|
+
parent: checkedMutationDirectory(parent.dir, parent.realPath, parentAfter),
|
|
41
|
+
child,
|
|
42
|
+
});
|
|
43
|
+
return Object.freeze({
|
|
44
|
+
receipt,
|
|
45
|
+
guard: Object.freeze({ dir: childPath, realPath, stat: childAfter }),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// Failed optional evidence leaves the existing ordered admission in charge.
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function sameAbsolutePath(left, right) {
|
|
54
|
+
return path.relative(path.resolve(left), path.resolve(right)) === "";
|
|
55
|
+
}
|
|
10
56
|
function suppliedExactRootIdentity(identity) {
|
|
11
57
|
return typeof identity?.dev === "bigint" && typeof identity.ino === "bigint"
|
|
12
58
|
? { dev: identity.dev, ino: identity.ino }
|
|
@@ -17,9 +63,14 @@ function assertGuardMatchesRootIdentity(guard, expected) {
|
|
|
17
63
|
throw rootPathChangedError();
|
|
18
64
|
}
|
|
19
65
|
}
|
|
66
|
+
function isPromiseLike(value) {
|
|
67
|
+
return typeof value?.then === "function";
|
|
68
|
+
}
|
|
20
69
|
async function realpathOrThrowNotFile(target) {
|
|
21
70
|
try {
|
|
22
|
-
|
|
71
|
+
const canonical = realpathSync.native(pathForWindowsFilesystem(target));
|
|
72
|
+
assertNoWindowsPathAlias(canonical, "filesystem", "canonical directory uses a Windows filesystem namespace alias");
|
|
73
|
+
return resolvePathPreservingWindowsRoot(canonical);
|
|
23
74
|
}
|
|
24
75
|
catch (error) {
|
|
25
76
|
if (isNotFoundPathError(error)) {
|
|
@@ -37,20 +88,25 @@ async function realpathOrThrowNotFile(target) {
|
|
|
37
88
|
* re-deriving it from the original, possibly-symlinked, lexical path.
|
|
38
89
|
*/
|
|
39
90
|
export async function mkdirPathComponentsWithGuards(params) {
|
|
40
|
-
const
|
|
41
|
-
|
|
91
|
+
const rawRootReal = params.rootReal;
|
|
92
|
+
assertNoWindowsPathAlias(rawRootReal, "filesystem", "root directory uses a Windows filesystem namespace alias");
|
|
93
|
+
const rawTargetPath = params.targetPath;
|
|
94
|
+
assertNoWindowsPathAlias(rawTargetPath, "filesystem", "target directory uses a Windows filesystem namespace alias");
|
|
95
|
+
const root = resolvePathPreservingWindowsRoot(rawRootReal);
|
|
96
|
+
const target = resolvePathPreservingWindowsRoot(rawTargetPath);
|
|
42
97
|
const suppliedIdentity = suppliedExactRootIdentity(params.rootIdentity);
|
|
98
|
+
const configuredRootGuard = await createAsyncDirectoryGuard(root, { bigint: true });
|
|
43
99
|
const checkedRootIdentity = suppliedIdentity ?? {
|
|
44
100
|
dev: configuredRootGuard.stat.dev,
|
|
45
101
|
ino: configuredRootGuard.stat.ino,
|
|
46
102
|
};
|
|
47
103
|
assertGuardMatchesRootIdentity(configuredRootGuard, checkedRootIdentity);
|
|
48
|
-
|
|
104
|
+
assertNoWindowsPathAlias(configuredRootGuard.realPath);
|
|
105
|
+
const rootCanonical = resolvePathPreservingWindowsRoot(configuredRootGuard.realPath);
|
|
49
106
|
const rootGuard = rootCanonical === root
|
|
50
107
|
? configuredRootGuard
|
|
51
108
|
: await createAsyncDirectoryGuard(rootCanonical, { bigint: true });
|
|
52
109
|
assertGuardMatchesRootIdentity(rootGuard, checkedRootIdentity);
|
|
53
|
-
const target = path.resolve(params.targetPath);
|
|
54
110
|
const admissionParams = {
|
|
55
111
|
candidatePath: target,
|
|
56
112
|
rootIdentity: checkedRootIdentity,
|
|
@@ -67,37 +123,119 @@ export async function mkdirPathComponentsWithGuards(params) {
|
|
|
67
123
|
}
|
|
68
124
|
let current = rootCanonical;
|
|
69
125
|
let currentGuard = rootGuard;
|
|
70
|
-
|
|
126
|
+
const parts = admittedTarget.relativePath.split(path.sep).filter(Boolean);
|
|
127
|
+
let partRoute;
|
|
128
|
+
let retainedTargetPath = params.retainedTargetPath &&
|
|
129
|
+
sameAbsolutePath(path.dirname(params.retainedTargetPath), target)
|
|
130
|
+
? params.retainedTargetPath
|
|
131
|
+
: undefined;
|
|
132
|
+
let retainedParentPath = retainedTargetPath ? path.dirname(retainedTargetPath) : undefined;
|
|
133
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
134
|
+
const part = parts[index];
|
|
71
135
|
const next = path.join(current, part);
|
|
72
136
|
const parentGuard = currentGuard;
|
|
73
|
-
|
|
137
|
+
assertNoWindowsPathAlias(parentGuard.realPath, "filesystem", "canonical parent directory uses a Windows filesystem namespace alias");
|
|
138
|
+
let created = false;
|
|
139
|
+
let createReceipt;
|
|
140
|
+
if (!params.revalidateParentAfterBeforeComponent) {
|
|
141
|
+
await assertAsyncDirectoryGuard(parentGuard);
|
|
142
|
+
}
|
|
74
143
|
await params.beforeComponent?.(next);
|
|
75
|
-
params.
|
|
76
|
-
|
|
77
|
-
|
|
144
|
+
if (params.revalidateParentAfterBeforeComponent) {
|
|
145
|
+
await assertAsyncDirectoryGuard(parentGuard);
|
|
146
|
+
}
|
|
147
|
+
if (params.beforeCreateComponent) {
|
|
148
|
+
let missing = false;
|
|
149
|
+
try {
|
|
150
|
+
fsSync.lstatSync(next);
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
if (!isNotFoundPathError(error))
|
|
154
|
+
throw error;
|
|
155
|
+
missing = true;
|
|
156
|
+
}
|
|
157
|
+
if (missing) {
|
|
158
|
+
let prospectiveParent = retainedParentPath;
|
|
159
|
+
if (!prospectiveParent) {
|
|
160
|
+
partRoute ??= createPathSegmentRoute(parts);
|
|
161
|
+
prospectiveParent = joinPathSegmentRoute(next, partRoute, index + 1);
|
|
162
|
+
}
|
|
163
|
+
const authorization = params.beforeCreateComponent(next, prospectiveParent, retainedTargetPath, observedGuard(parentGuard));
|
|
164
|
+
if (isPromiseLike(authorization)) {
|
|
165
|
+
createReceipt = await authorization;
|
|
166
|
+
await assertAsyncDirectoryGuard(parentGuard);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
createReceipt = authorization;
|
|
170
|
+
// A synchronous authorization must still end at a live pathname
|
|
171
|
+
// fence immediately before the authority callback and mkdir.
|
|
172
|
+
if (!params.synchronousAuthorizationIncludesFence)
|
|
173
|
+
inspectGuardCurrent(parentGuard);
|
|
174
|
+
}
|
|
175
|
+
params.assertBeforeMutation?.();
|
|
176
|
+
// Authority callbacks are an explicit freshness boundary. Recheck the
|
|
177
|
+
// exact parent synchronously so no callback-observed authority is
|
|
178
|
+
// carried into a pathname mkdir after the parent was swapped.
|
|
179
|
+
inspectGuardCurrent(parentGuard);
|
|
180
|
+
try {
|
|
181
|
+
await fs.mkdir(next, { mode: params.mode });
|
|
182
|
+
created = true;
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
if (!error || typeof error !== "object" || !("code" in error) || error.code !== "EEXIST") {
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
params.assertBeforeMutation?.();
|
|
193
|
+
try {
|
|
194
|
+
await fs.mkdir(next, { mode: params.mode });
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
if (!error || typeof error !== "object" || !("code" in error) || error.code !== "EEXIST") {
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
let createdAuthorization;
|
|
203
|
+
let createdEvidence;
|
|
204
|
+
if (created && createReceipt && params.afterCreateComponent) {
|
|
205
|
+
createdEvidence = createdDirectoryEvidence(createReceipt, parentGuard, next);
|
|
206
|
+
if (createdEvidence) {
|
|
207
|
+
createdAuthorization = params.afterCreateComponent(createdEvidence.receipt);
|
|
208
|
+
}
|
|
78
209
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
210
|
+
if (params.beforeUseComponent && !createdAuthorization) {
|
|
211
|
+
let prospectiveParent = retainedParentPath;
|
|
212
|
+
if (!prospectiveParent) {
|
|
213
|
+
partRoute ??= createPathSegmentRoute(parts);
|
|
214
|
+
prospectiveParent = joinPathSegmentRoute(next, partRoute, index + 1);
|
|
82
215
|
}
|
|
216
|
+
await params.beforeUseComponent(next, prospectiveParent, retainedTargetPath);
|
|
217
|
+
await assertAsyncDirectoryGuard(parentGuard);
|
|
83
218
|
}
|
|
84
|
-
const stat = fsSync.lstatSync(next);
|
|
85
|
-
if ((params.rejectSymlinks && stat.isSymbolicLink()) ||
|
|
219
|
+
const stat = createdAuthorization ? undefined : fsSync.lstatSync(next);
|
|
220
|
+
if (stat && ((params.rejectSymlinks && stat.isSymbolicLink()) ||
|
|
221
|
+
(!stat.isSymbolicLink() && !stat.isDirectory()))) {
|
|
86
222
|
throw directoryComponentNotDirectoryError();
|
|
87
223
|
}
|
|
88
224
|
// Node's recursive mkdir follows symlinks in missing components. Build one
|
|
89
225
|
// segment at a time and realpath-check each segment before descending.
|
|
90
|
-
const
|
|
226
|
+
const nextReal = createdAuthorization && createdEvidence
|
|
227
|
+
? createdEvidence.receipt.child.canonicalPath
|
|
228
|
+
: await realpathOrThrowNotFile(next);
|
|
91
229
|
const admittedNextReal = admitPathInsideRoot({
|
|
92
230
|
rootPath: rootCanonical,
|
|
93
|
-
candidatePath:
|
|
231
|
+
candidatePath: nextReal,
|
|
94
232
|
rootIdentity: checkedRootIdentity,
|
|
95
233
|
});
|
|
96
234
|
if (!admittedNextReal) {
|
|
97
235
|
throw new FsSafeError("outside-workspace", "directory escaped workspace root");
|
|
98
236
|
}
|
|
99
|
-
const
|
|
100
|
-
if (stat
|
|
237
|
+
const admittedNextPath = admittedNextReal.path;
|
|
238
|
+
if (stat?.isSymbolicLink()) {
|
|
101
239
|
// An existing path component may legitimately be a symlink to a real
|
|
102
240
|
// directory inside the root (e.g. a skill-bank layout). We already
|
|
103
241
|
// verified above that it resolves inside the root, so treat the
|
|
@@ -107,18 +245,29 @@ export async function mkdirPathComponentsWithGuards(params) {
|
|
|
107
245
|
// every subsequent segment. Callers that need the final directory
|
|
108
246
|
// (e.g. to guard it themselves after this function returns) must use
|
|
109
247
|
// the returned resolved path, not their own lexical parent path.
|
|
110
|
-
const targetStat = fsSync.statSync(
|
|
248
|
+
const targetStat = fsSync.statSync(admittedNextPath);
|
|
111
249
|
if (!targetStat.isDirectory()) {
|
|
112
250
|
throw directoryComponentNotDirectoryError();
|
|
113
251
|
}
|
|
114
|
-
currentGuard = await createAsyncDirectoryGuard(
|
|
252
|
+
currentGuard = await createAsyncDirectoryGuard(admittedNextPath, { bigint: true });
|
|
253
|
+
assertNoWindowsPathAlias(currentGuard.realPath, "filesystem", "canonical directory uses a Windows filesystem namespace alias");
|
|
115
254
|
await assertAsyncDirectoryGuard(parentGuard);
|
|
116
|
-
|
|
255
|
+
retainedTargetPath = undefined;
|
|
256
|
+
retainedParentPath = undefined;
|
|
257
|
+
current = admittedNextPath;
|
|
117
258
|
continue;
|
|
118
259
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
260
|
+
if (createdAuthorization && createdEvidence) {
|
|
261
|
+
currentGuard = createdEvidence.guard;
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
currentGuard = await createAsyncDirectoryGuard(admittedNextPath, { bigint: true });
|
|
265
|
+
}
|
|
266
|
+
assertNoWindowsPathAlias(currentGuard.realPath, "filesystem", "canonical directory uses a Windows filesystem namespace alias");
|
|
267
|
+
if (!createdAuthorization || !createdEvidence) {
|
|
268
|
+
await assertAsyncDirectoryGuard(parentGuard);
|
|
269
|
+
}
|
|
270
|
+
current = admittedNextPath;
|
|
122
271
|
}
|
|
123
272
|
return current;
|
|
124
273
|
}
|
package/dist/guest.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guest.d.ts","sourceRoot":"","sources":["../src/guest.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,yCAAyC,EAC1C,MAAM,0BAA0B,CAAC;AAElC,yFAAyF;AACzF,eAAO,MAAM,wCAAwC,KAAK,CAAC;AAC3D,8EAA8E;AAC9E,eAAO,MAAM,yCAAyC,IAAI,CAAC;AAE3D,gGAAgG;AAChG,eAAO,MAAM,uBAAuB,
|
|
1
|
+
{"version":3,"file":"guest.d.ts","sourceRoot":"","sources":["../src/guest.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,yCAAyC,EAC1C,MAAM,0BAA0B,CAAC;AAElC,yFAAyF;AACzF,eAAO,MAAM,wCAAwC,KAAK,CAAC;AAC3D,8EAA8E;AAC9E,eAAO,MAAM,yCAAyC,IAAI,CAAC;AAE3D,gGAAgG;AAChG,eAAO,MAAM,uBAAuB,QA8ZxB,CAAC;AAEb,OAAO,EAAE,yCAAyC,EAAE,CAAC"}
|
package/dist/guest.js
CHANGED
|
@@ -57,7 +57,10 @@ export const GUEST_FILESYSTEM_PYTHON = [
|
|
|
57
57
|
" except FileNotFoundError:",
|
|
58
58
|
" if not mkdir_enabled:",
|
|
59
59
|
" raise",
|
|
60
|
-
"
|
|
60
|
+
" try:",
|
|
61
|
+
" os.mkdir(segment, 0o777, dir_fd=current_fd)",
|
|
62
|
+
" except FileExistsError:",
|
|
63
|
+
" pass # The competing entry must still pass the no-follow directory open.",
|
|
61
64
|
" next_fd = open_dir(segment, dir_fd=current_fd)",
|
|
62
65
|
" os.close(current_fd)",
|
|
63
66
|
" current_fd = next_fd",
|
package/dist/home-dir.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"home-dir.d.ts","sourceRoot":"","sources":["../src/home-dir.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"home-dir.d.ts","sourceRoot":"","sources":["../src/home-dir.ts"],"names":[],"mappings":"AAsCA,wBAAgB,uBAAuB,CACrC,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,OAAO,GAAE,MAAM,MAAmB,GACjC,MAAM,GAAG,SAAS,CAOpB;AAwCD,wBAAgB,sBAAsB,CACpC,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,OAAO,GAAE,MAAM,MAAmB,GACjC,MAAM,CAIR;AAED,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IACL,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,MAAM,CAAC;CACxB,GACA,MAAM,CAYR;AA4DD,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IACL,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,MAAM,CAAC;CACxB,GACA,MAAM,CAoBR"}
|
package/dist/home-dir.js
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
3
4
|
import { normalizeOptionalString } from "./string-coerce.js";
|
|
5
|
+
import { assertNoWindowsPathAlias, assertNoWindowsPathAliasForPlatform, repairResolvedWindowsRoot, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
6
|
+
const PATH_ALIAS_MESSAGE = "path uses a Windows filesystem namespace alias";
|
|
7
|
+
function isOrdinaryRootedWindowsDrivePath(input) {
|
|
8
|
+
const drive = input.charCodeAt(0);
|
|
9
|
+
return input.length >= 3 &&
|
|
10
|
+
((drive >= 0x41 && drive <= 0x5a) || (drive >= 0x61 && drive <= 0x7a)) &&
|
|
11
|
+
input.charCodeAt(1) === 0x3a &&
|
|
12
|
+
(input.charCodeAt(2) === 0x5c || input.charCodeAt(2) === 0x2f) &&
|
|
13
|
+
input.indexOf(":", 2) === -1;
|
|
14
|
+
}
|
|
4
15
|
function hasHomePrefix(input) {
|
|
5
16
|
return input === "~" || input.startsWith("~/") ||
|
|
6
17
|
(path.sep === "\\" && input.startsWith("~\\"));
|
|
@@ -17,7 +28,12 @@ function normalize(value) {
|
|
|
17
28
|
}
|
|
18
29
|
export function resolveEffectiveHomeDir(env = process.env, homedir = os.homedir) {
|
|
19
30
|
const raw = resolveRawHomeDir(env, homedir);
|
|
20
|
-
|
|
31
|
+
if (!raw)
|
|
32
|
+
return undefined;
|
|
33
|
+
assertNoWindowsPathAlias(raw, "filesystem", "home path uses a Windows filesystem namespace alias");
|
|
34
|
+
const resolved = resolvePathPreservingWindowsRoot(raw);
|
|
35
|
+
assertNoWindowsPathAlias(resolved, "filesystem", "home path uses a Windows filesystem namespace alias");
|
|
36
|
+
return resolved;
|
|
21
37
|
}
|
|
22
38
|
function resolveRawHomeDir(env, homedir) {
|
|
23
39
|
const explicitHome = normalize(env.OPENCLAW_HOME);
|
|
@@ -56,7 +72,9 @@ function normalizeSafe(homedir) {
|
|
|
56
72
|
}
|
|
57
73
|
}
|
|
58
74
|
export function resolveRequiredHomeDir(env = process.env, homedir = os.homedir) {
|
|
59
|
-
|
|
75
|
+
const resolved = resolveEffectiveHomeDir(env, homedir) ?? path.resolve(process.cwd());
|
|
76
|
+
assertNoWindowsPathAlias(resolved, "filesystem", "home path uses a Windows filesystem namespace alias");
|
|
77
|
+
return resolved;
|
|
60
78
|
}
|
|
61
79
|
export function expandHomePrefix(input, opts) {
|
|
62
80
|
if (!hasHomePrefix(input)) {
|
|
@@ -70,17 +88,62 @@ export function expandHomePrefix(input, opts) {
|
|
|
70
88
|
// Expand before normalizing so a following .. traverses the actual home.
|
|
71
89
|
return path.join(home, input.slice(2));
|
|
72
90
|
}
|
|
73
|
-
|
|
74
|
-
if (!input) {
|
|
75
|
-
return input;
|
|
76
|
-
}
|
|
77
|
-
if (!hasHomePrefix(input)) {
|
|
78
|
-
return path.resolve(input);
|
|
79
|
-
}
|
|
91
|
+
function resolveExpandedHomePath(input, opts) {
|
|
80
92
|
const expanded = expandHomePrefix(input, {
|
|
81
93
|
home: resolveRequiredHomeDir(opts?.env ?? process.env, opts?.homedir ?? os.homedir),
|
|
82
94
|
env: opts?.env,
|
|
83
95
|
homedir: opts?.homedir,
|
|
84
96
|
});
|
|
85
|
-
|
|
97
|
+
const resolved = resolvePathPreservingWindowsRoot(expanded);
|
|
98
|
+
assertNoWindowsPathAlias(resolved, "filesystem", PATH_ALIAS_MESSAGE);
|
|
99
|
+
return resolved;
|
|
100
|
+
}
|
|
101
|
+
function admitFinalHomePath(resolved, platform) {
|
|
102
|
+
assertNoWindowsPathAliasForPlatform(resolved, "filesystem", PATH_ALIAS_MESSAGE, platform);
|
|
103
|
+
return resolved;
|
|
104
|
+
}
|
|
105
|
+
function finishOrdinaryHomePath(input, resolved) {
|
|
106
|
+
const resolvedPlatform = process.platform;
|
|
107
|
+
if (resolved === input && resolvedPlatform !== undefined)
|
|
108
|
+
return resolved;
|
|
109
|
+
return admitFinalHomePath(resolved, resolvedPlatform);
|
|
110
|
+
}
|
|
111
|
+
function repairAndFinishOrdinaryHomePath(input, resolved) {
|
|
112
|
+
return finishOrdinaryHomePath(input, repairResolvedWindowsRoot(input, resolved));
|
|
113
|
+
}
|
|
114
|
+
function resolveHomePathCold(input, opts, rawPlatform, ordinaryRawAdmitted) {
|
|
115
|
+
if (ordinaryRawAdmitted) {
|
|
116
|
+
return finishOrdinaryHomePath(input, resolvePathPreservingWindowsRoot(input));
|
|
117
|
+
}
|
|
118
|
+
assertNoWindowsPathAliasForPlatform(input, "filesystem", PATH_ALIAS_MESSAGE, rawPlatform);
|
|
119
|
+
if (!hasHomePrefix(input)) {
|
|
120
|
+
const resolved = resolvePathPreservingWindowsRoot(input);
|
|
121
|
+
// This cold branch was not admitted as an ordinary rooted drive. Recheck
|
|
122
|
+
// even an unchanged result because the live platform can change between
|
|
123
|
+
// raw admission and resolved-path admission. Passing undefined preserves
|
|
124
|
+
// the classifier's default process.platform read.
|
|
125
|
+
return admitFinalHomePath(resolved, process.platform);
|
|
126
|
+
}
|
|
127
|
+
return resolveExpandedHomePath(input, opts);
|
|
128
|
+
}
|
|
129
|
+
export function resolveHomeRelativePath(input, opts) {
|
|
130
|
+
if (!input) {
|
|
131
|
+
return input;
|
|
132
|
+
}
|
|
133
|
+
const rawPlatform = process.platform;
|
|
134
|
+
// Primitive ordinary drive paths have exactly one structural colon. Record
|
|
135
|
+
// that admission so an unchanged resolved string does not need rescanning.
|
|
136
|
+
const ordinaryRawAdmitted = rawPlatform === "win32" &&
|
|
137
|
+
typeof input === "string" &&
|
|
138
|
+
isOrdinaryRootedWindowsDrivePath(input);
|
|
139
|
+
// Keep the overwhelmingly common rooted-drive path close to path.resolve
|
|
140
|
+
// itself. Seven-byte inputs retain the general helper's observable
|
|
141
|
+
// namespace-root preflight; a rare six-byte result retains its repair.
|
|
142
|
+
if (!ordinaryRawAdmitted || input.length === 7) {
|
|
143
|
+
return resolveHomePathCold(input, opts, rawPlatform, ordinaryRawAdmitted);
|
|
144
|
+
}
|
|
145
|
+
const resolved = path.resolve(input);
|
|
146
|
+
return resolved.length === 6
|
|
147
|
+
? repairAndFinishOrdinaryHomePath(input, resolved)
|
|
148
|
+
: finishOrdinaryHomePath(input, resolved);
|
|
86
149
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"install-path.d.ts","sourceRoot":"","sources":["../src/install-path.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"install-path.d.ts","sourceRoot":"","sources":["../src/install-path.ts"],"names":[],"mappings":"AAWA,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMjD;AAED,2FAA2F;AAC3F,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAiB3D;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAO7D;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,kBAAkB,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;CACtC,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CA+B5D;AAED,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuEhB"}
|