@openclaw/openshell-sandbox 2026.9.6 → 2026.9.8
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/assets/icon.png +0 -0
- package/dist/index.js +75 -229
- package/node_modules/@openclaw/fs-safe/CHANGELOG.md +102 -0
- package/node_modules/@openclaw/fs-safe/README.md +24 -6
- package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +6 -1
- package/node_modules/@openclaw/fs-safe/dist/advanced.js +3 -1
- package/node_modules/@openclaw/fs-safe/dist/archive-durability.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/archive-merge.js +1 -2
- package/node_modules/@openclaw/fs-safe/dist/archive-plan.d.ts +2 -7
- package/node_modules/@openclaw/fs-safe/dist/archive-read.js +9 -18
- package/node_modules/@openclaw/fs-safe/dist/archive-staging.d.ts +1 -2
- package/node_modules/@openclaw/fs-safe/dist/archive-staging.js +3 -3
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-directory.js +4 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-entry.d.ts +11 -11
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-entry.js +3 -35
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-integrity.d.ts +2 -2
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-integrity.js +2 -12
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-loader.d.ts +7 -3
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-loader.js +13 -10
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-manifest.js +3 -1
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-preflight.d.ts +2 -3
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-preflight.js +16 -8
- package/node_modules/@openclaw/fs-safe/dist/archive.d.ts +6 -3
- package/node_modules/@openclaw/fs-safe/dist/archive.js +22 -19
- package/node_modules/@openclaw/fs-safe/dist/atomic.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/copy-publication.d.ts +1 -3
- package/node_modules/@openclaw/fs-safe/dist/copy-publication.js +2 -6
- package/node_modules/@openclaw/fs-safe/dist/deny-mutation-match.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/deny-mutation-match.js +71 -0
- package/node_modules/@openclaw/fs-safe/dist/deny-mutations.js +4 -3
- package/node_modules/@openclaw/fs-safe/dist/directory-guard.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/directory-guard.js +0 -3
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.d.ts +20 -2
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.js +77 -1
- package/node_modules/@openclaw/fs-safe/dist/directory-receipt.js +5 -7
- package/node_modules/@openclaw/fs-safe/dist/effective-uid.js +1 -4
- package/node_modules/@openclaw/fs-safe/dist/errors.d.ts +3 -1
- package/node_modules/@openclaw/fs-safe/dist/errors.js +3 -2
- package/node_modules/@openclaw/fs-safe/dist/file-identity.js +10 -2
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-acquisition.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-admission.js +5 -1
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-held.d.ts +4 -11
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-held.js +1 -4
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-io.d.ts +3 -6
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-io.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-options.d.ts +2 -10
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root.d.ts +2 -4
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/file-store-boundary.d.ts +3 -7
- package/node_modules/@openclaw/fs-safe/dist/file-store-boundary.js +7 -10
- package/node_modules/@openclaw/fs-safe/dist/file-store-prune.js +7 -6
- package/node_modules/@openclaw/fs-safe/dist/file-store.d.ts +4 -7
- package/node_modules/@openclaw/fs-safe/dist/file-store.js +19 -21
- package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +23 -31
- package/node_modules/@openclaw/fs-safe/dist/guest.js +21 -12
- package/node_modules/@openclaw/fs-safe/dist/json-document-store.d.ts +4 -9
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-ownership.d.ts +1 -5
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue.js +2 -7
- package/node_modules/@openclaw/fs-safe/dist/local-file-access.js +2 -5
- package/node_modules/@openclaw/fs-safe/dist/local-file-descriptor.d.ts +2 -5
- package/node_modules/@openclaw/fs-safe/dist/local-roots.d.ts +2 -7
- package/node_modules/@openclaw/fs-safe/dist/move-path-cleanup.js +4 -4
- package/node_modules/@openclaw/fs-safe/dist/mutation-authority.js +5 -0
- package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +51 -14
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +8 -2
- package/node_modules/@openclaw/fs-safe/dist/native-staged-symlink.d.ts +13 -0
- package/node_modules/@openclaw/fs-safe/dist/native-staged-symlink.js +303 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch-worker.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch-worker.js +54 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch.d.ts +5 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch.js +64 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl.js +3 -0
- package/node_modules/@openclaw/fs-safe/dist/path.js +17 -1
- package/node_modules/@openclaw/fs-safe/dist/permission-exec.d.ts +6 -0
- package/node_modules/@openclaw/fs-safe/dist/permission-exec.js +3 -6
- package/node_modules/@openclaw/fs-safe/dist/permissions-public.d.ts +3 -1
- package/node_modules/@openclaw/fs-safe/dist/permissions-public.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/permissions-windows.d.ts +3 -6
- package/node_modules/@openclaw/fs-safe/dist/permissions.d.ts +3 -10
- package/node_modules/@openclaw/fs-safe/dist/permissions.js +0 -1
- package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-admission.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-admission.js +4 -2
- package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-shared-route.d.ts +2 -8
- package/node_modules/@openclaw/fs-safe/dist/pinned-open.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/pinned-open.js +2 -3
- package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +4 -0
- package/node_modules/@openclaw/fs-safe/dist/read-opened-file.d.ts +2 -5
- package/node_modules/@openclaw/fs-safe/dist/regular-file.js +3 -3
- package/node_modules/@openclaw/fs-safe/dist/replace-directory.js +5 -17
- package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.js +36 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.d.ts +4 -10
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.js +66 -38
- package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.d.ts +7 -11
- package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +10 -2
- package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.d.ts +21 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.js +123 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-mutation.d.ts +26 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-mutation.js +47 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.d.ts +2 -2
- package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.js +16 -4
- package/node_modules/@openclaw/fs-safe/dist/replace-file-types.d.ts +55 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file.d.ts +3 -55
- package/node_modules/@openclaw/fs-safe/dist/replace-file.js +31 -11
- package/node_modules/@openclaw/fs-safe/dist/retained-directory-replacement.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/retained-directory-replacement.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/retained-file-types.d.ts +50 -0
- package/node_modules/@openclaw/fs-safe/dist/retained-file-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/retained-file.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/retained-file.js +121 -0
- package/node_modules/@openclaw/fs-safe/dist/root-context.js +3 -2
- package/node_modules/@openclaw/fs-safe/dist/root-directory-entry.d.ts +9 -0
- package/node_modules/@openclaw/fs-safe/dist/root-directory-entry.js +28 -0
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.d.ts +9 -1
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +88 -37
- package/node_modules/@openclaw/fs-safe/dist/root-file-final-admission.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/root-file-final-admission.js +1 -6
- package/node_modules/@openclaw/fs-safe/dist/root-file.js +2 -5
- package/node_modules/@openclaw/fs-safe/dist/root-handle-context.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/root-handle-context.js +12 -0
- package/node_modules/@openclaw/fs-safe/dist/root-impl.d.ts +3 -3
- package/node_modules/@openclaw/fs-safe/dist/root-impl.js +8 -5
- package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.d.ts +2 -7
- package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +3 -3
- package/node_modules/@openclaw/fs-safe/dist/root-observed-path.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/root-observed-path.js +0 -3
- package/node_modules/@openclaw/fs-safe/dist/root-path-observation.d.ts +4 -11
- package/node_modules/@openclaw/fs-safe/dist/root-path.js +7 -10
- package/node_modules/@openclaw/fs-safe/dist/root-paths.d.ts +2 -6
- package/node_modules/@openclaw/fs-safe/dist/root-remove-identity.d.ts +1 -3
- package/node_modules/@openclaw/fs-safe/dist/root-walk.d.ts +19 -12
- package/node_modules/@openclaw/fs-safe/dist/root-walk.js +56 -20
- package/node_modules/@openclaw/fs-safe/dist/root-write-admission.js +1 -6
- package/node_modules/@openclaw/fs-safe/dist/root-write-complete-parent.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/root-write-complete-parent.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.js +8 -2
- package/node_modules/@openclaw/fs-safe/dist/secret-file.d.ts +6 -2
- package/node_modules/@openclaw/fs-safe/dist/secret-file.js +12 -16
- package/node_modules/@openclaw/fs-safe/dist/secret-read-policy.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/secure-file-windows.js +5 -14
- package/node_modules/@openclaw/fs-safe/dist/secure-file.js +3 -2
- package/node_modules/@openclaw/fs-safe/dist/secure-temp-dir.js +2 -7
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js +4 -4
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-admission-parser.d.ts +1 -2
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-handle.d.ts +2 -8
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-policy.d.ts +2 -7
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.d.ts +2 -2
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js +11 -12
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-stale-admission.d.ts +1 -5
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock.js +9 -7
- package/node_modules/@openclaw/fs-safe/dist/staged-symlink-types.d.ts +39 -0
- package/node_modules/@openclaw/fs-safe/dist/staged-symlink-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/stat-observation.js +3 -10
- package/node_modules/@openclaw/fs-safe/dist/strict-file-identity.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/strict-file-identity.js +6 -6
- package/node_modules/@openclaw/fs-safe/dist/symlink-parents.js +58 -7
- package/node_modules/@openclaw/fs-safe/dist/temp-target.js +8 -10
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-admission.js +28 -33
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.js +16 -17
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-descriptor.js +1 -2
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-owner.js +4 -9
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.js +52 -0
- package/node_modules/@openclaw/fs-safe/dist/test-hooks.d.ts +5 -1
- package/node_modules/@openclaw/fs-safe/dist/text-atomic.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/text-atomic.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/trash.js +27 -1
- package/node_modules/@openclaw/fs-safe/dist/walk.d.ts +2 -5
- package/node_modules/@openclaw/fs-safe/dist/watch-alias.d.ts +6 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-alias.js +88 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-hints.d.ts +9 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-hints.js +93 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-native.d.ts +36 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-native.js +73 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-scan.d.ts +28 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-scan.js +300 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-stream.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-stream.js +32 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-types.d.ts +60 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/watch.d.ts +5 -0
- package/node_modules/@openclaw/fs-safe/dist/watch.js +530 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-owner.d.ts +3 -7
- package/node_modules/@openclaw/fs-safe/dist/windows-owner.js +0 -1
- package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.cs +6 -6
- package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.ps1 +78 -3
- package/node_modules/@openclaw/fs-safe/dist/windows-security-command.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-security-command.js +66 -15
- package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.js +6 -2
- package/node_modules/@openclaw/fs-safe/docs/advanced.md +4 -2
- package/node_modules/@openclaw/fs-safe/docs/archive.md +16 -0
- package/node_modules/@openclaw/fs-safe/docs/atomic.md +93 -3
- package/node_modules/@openclaw/fs-safe/docs/contributing.md +102 -4
- package/node_modules/@openclaw/fs-safe/docs/copy.md +2 -0
- package/node_modules/@openclaw/fs-safe/docs/durability.md +7 -0
- package/node_modules/@openclaw/fs-safe/docs/file-store.md +5 -0
- package/node_modules/@openclaw/fs-safe/docs/guest.md +7 -1
- package/node_modules/@openclaw/fs-safe/docs/index.md +1 -0
- package/node_modules/@openclaw/fs-safe/docs/install.md +30 -0
- package/node_modules/@openclaw/fs-safe/docs/native-helper.md +20 -4
- package/node_modules/@openclaw/fs-safe/docs/native.md +63 -1
- package/node_modules/@openclaw/fs-safe/docs/permissions.md +66 -0
- package/node_modules/@openclaw/fs-safe/docs/public-api.md +30 -3
- package/node_modules/@openclaw/fs-safe/docs/retained-file.md +115 -0
- package/node_modules/@openclaw/fs-safe/docs/root.md +35 -9
- package/node_modules/@openclaw/fs-safe/docs/secret-file.md +19 -0
- package/node_modules/@openclaw/fs-safe/docs/security-model.md +4 -1
- package/node_modules/@openclaw/fs-safe/docs/sidecar-lock.md +5 -1
- package/node_modules/@openclaw/fs-safe/docs/staged-symlink.md +124 -0
- package/node_modules/@openclaw/fs-safe/docs/store.md +3 -1
- package/node_modules/@openclaw/fs-safe/docs/temp.md +24 -4
- package/node_modules/@openclaw/fs-safe/docs/testing.md +210 -0
- package/node_modules/@openclaw/fs-safe/docs/types.md +6 -0
- package/node_modules/@openclaw/fs-safe/docs/walk.md +34 -1
- package/node_modules/@openclaw/fs-safe/docs/watch.md +251 -0
- package/node_modules/@openclaw/fs-safe/docs/writing.md +21 -0
- package/node_modules/@openclaw/fs-safe/package.json +13 -9
- package/node_modules/@openclaw/fs-safe-darwin-arm64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-darwin-arm64/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-darwin-x64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-darwin-x64/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-arm64-musl/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-arm64-musl/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-x64-gnu/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-x64-gnu/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-x64-musl/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-x64-musl/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-win32-x64-msvc/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-win32-x64-msvc/package.json +1 -1
- package/node_modules/p-limit/index.d.ts +2 -0
- package/node_modules/p-limit/index.js +17 -1
- package/node_modules/p-limit/package.json +1 -1
- package/node_modules/p-limit/readme.md +2 -0
- package/package.json +6 -6
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-owner.d.ts +0 -20
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-owner.js +0 -78
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import { type BigIntStats, type Stats } from "node:fs";
|
|
2
|
-
import {
|
|
2
|
+
import type { RootMoveOptions } from "./root-options.js";
|
|
3
3
|
import { type RootContext } from "./root-context.js";
|
|
4
|
-
import { type MutationSymlinkPolicy } from "./root-symlink-policy.js";
|
|
5
4
|
export declare function admitMoveSourceStat<T extends Stats | BigIntStats>(stat: T, overwrite?: boolean): T;
|
|
6
|
-
export declare function movePathNoReplaceNative(root: RootContext, params: {
|
|
7
|
-
assertBeforeMutation?: () => void;
|
|
8
|
-
denyMutations?: DenyMutationPolicy;
|
|
9
|
-
mutationSymlinks?: MutationSymlinkPolicy;
|
|
10
|
-
}, paths: {
|
|
5
|
+
export declare function movePathNoReplaceNative(root: RootContext, params: Pick<RootMoveOptions, "assertBeforeMutation" | "denyMutations" | "mutationSymlinks">, paths: {
|
|
11
6
|
sourcePath: string;
|
|
12
7
|
sourceParentPath: string;
|
|
13
8
|
targetPath: string;
|
|
@@ -4,7 +4,7 @@ import { assertAsyncDirectoryGuard, assertSyncDirectoryGuard } from "./directory
|
|
|
4
4
|
import { FsSafeError } from "./errors.js";
|
|
5
5
|
import { assertMutationNotDenied } from "./deny-mutations.js";
|
|
6
6
|
import { openNativeParentAdmission, openNativeRootAdmission, } from "./native-parent-admission.js";
|
|
7
|
-
import {
|
|
7
|
+
import { requireNativeBinding } from "./native.js";
|
|
8
8
|
import { isNotFoundPathError } from "./path.js";
|
|
9
9
|
import { assertRootIdentityCurrent, assertRootIdentityCurrentSync } from "./root-context.js";
|
|
10
10
|
import { resolveRootPathSync } from "./root-path.js";
|
|
@@ -78,8 +78,8 @@ export async function movePathNoReplaceNative(root, params, paths) {
|
|
|
78
78
|
throw error;
|
|
79
79
|
// Advisory fast rejection only. renameNoReplace owns the collision decision.
|
|
80
80
|
}
|
|
81
|
-
const binding =
|
|
82
|
-
if (
|
|
81
|
+
const binding = requireNativeBinding();
|
|
82
|
+
if (typeof binding.renameNoReplace !== "function") {
|
|
83
83
|
throw new FsSafeError("helper-unavailable", "native no-replace move is unavailable");
|
|
84
84
|
}
|
|
85
85
|
const rootAdmission = await openNativeRootAdmission(binding, {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { type RootPathObservationKind, type RootPathObservationReceipt } from "./root-path.js";
|
|
2
2
|
import { type RootContext } from "./root-context.js";
|
|
3
3
|
export type PinnedObservedPath = {
|
|
4
|
-
rootReal: string;
|
|
5
4
|
resolved: string;
|
|
6
5
|
receipt?: RootPathObservationReceipt;
|
|
7
6
|
};
|
|
@@ -63,7 +63,6 @@ export async function resolvePinnedObservedPathInRoot(root, relativePath, kind)
|
|
|
63
63
|
// A receipt is emitted only for the straight traversal whose lexical and
|
|
64
64
|
// canonical cursors stayed identical and inside the checked boundary.
|
|
65
65
|
return {
|
|
66
|
-
rootReal: resolved.rootCanonicalPath,
|
|
67
66
|
resolved: resolved.canonicalPath,
|
|
68
67
|
receipt: observed.receipt,
|
|
69
68
|
};
|
|
@@ -71,7 +70,6 @@ export async function resolvePinnedObservedPathInRoot(root, relativePath, kind)
|
|
|
71
70
|
const relativeResolved = path.relative(resolved.rootCanonicalPath, resolved.canonicalPath);
|
|
72
71
|
if (relativeResolved === "" || relativeResolved === ".") {
|
|
73
72
|
return {
|
|
74
|
-
rootReal: resolved.rootCanonicalPath,
|
|
75
73
|
resolved: resolved.canonicalPath,
|
|
76
74
|
};
|
|
77
75
|
}
|
|
@@ -87,7 +85,6 @@ export async function resolvePinnedObservedPathInRoot(root, relativePath, kind)
|
|
|
87
85
|
if (!admittedCanonicalPath)
|
|
88
86
|
throw outsideWorkspaceError();
|
|
89
87
|
return {
|
|
90
|
-
rootReal: resolved.rootCanonicalPath,
|
|
91
88
|
resolved: admittedCanonicalPath.path,
|
|
92
89
|
};
|
|
93
90
|
}
|
|
@@ -7,14 +7,11 @@ export type RootPathObservationKind = "stat" | "directory";
|
|
|
7
7
|
export type RootPathDirectoryObservationGuard = DirectoryObservationGuard | NativeDirectoryObservationGuard;
|
|
8
8
|
export type RootPathTargetObservation = StatObservationReceipt | NativeDirectoryObservationGuard;
|
|
9
9
|
/** An exact receipt owned by one stat/list operation. Never cache it. */
|
|
10
|
-
export type RootPathObservationReceipt = {
|
|
11
|
-
kind: RootPathObservationKind;
|
|
12
|
-
rootGuard: DirectoryObservationGuard;
|
|
10
|
+
export type RootPathObservationReceipt = Pick<RootPathObservationRequest & {
|
|
13
11
|
directoryGuard: RootPathDirectoryObservationGuard;
|
|
14
|
-
directoryObserver?: NativeDirectoryObservationBackend;
|
|
15
12
|
targetPath: string;
|
|
16
13
|
target: RootPathTargetObservation;
|
|
17
|
-
}
|
|
14
|
+
}, "kind" | "rootGuard" | "directoryGuard" | "directoryObserver" | "targetPath" | "target">;
|
|
18
15
|
/** A failed initial target lookup must not discard its already admitted parent. */
|
|
19
16
|
export type RootPathParentObservationReceipt = Omit<RootPathObservationReceipt, "kind" | "target"> & {
|
|
20
17
|
kind: "stat-parent";
|
|
@@ -24,16 +21,12 @@ export type RootPathObservationRequest = {
|
|
|
24
21
|
rootGuard: DirectoryObservationGuard;
|
|
25
22
|
directoryObserver?: NativeDirectoryObservationBackend;
|
|
26
23
|
};
|
|
27
|
-
export type RootPathTraversalObservation = {
|
|
24
|
+
export type RootPathTraversalObservation = Omit<Partial<RootPathObservationReceipt> & {
|
|
28
25
|
enabled: boolean;
|
|
29
26
|
request: RootPathObservationRequest;
|
|
30
27
|
targetIndex: number;
|
|
31
28
|
directoryIndex: number;
|
|
32
|
-
|
|
33
|
-
directoryObserver?: NativeDirectoryObservationBackend;
|
|
34
|
-
targetPath?: string;
|
|
35
|
-
target?: RootPathTargetObservation;
|
|
36
|
-
};
|
|
29
|
+
}, "kind" | "rootGuard">;
|
|
37
30
|
export type RootPathObservedTraversalEntry = StatObservationReceipt | {
|
|
38
31
|
stat: fs.Stats | BigIntStats;
|
|
39
32
|
identity?: undefined;
|
|
@@ -214,8 +214,8 @@ function finalizeLexicalResolution(context, kind) {
|
|
|
214
214
|
rootPath: context.rootPath,
|
|
215
215
|
rootCanonicalPath: context.rootCanonicalPath,
|
|
216
216
|
relativePath: relativeInsideRoot(context.rootCanonicalPath, context.state.canonicalCursor),
|
|
217
|
-
exists: kind
|
|
218
|
-
kind
|
|
217
|
+
exists: kind !== "missing",
|
|
218
|
+
kind,
|
|
219
219
|
};
|
|
220
220
|
}
|
|
221
221
|
function applyResolvedSymlinkHop(context, linkCanonical) {
|
|
@@ -374,12 +374,9 @@ function traverseRootPath(params, { mode = "native", observation: observationOut
|
|
|
374
374
|
const completeObservation = observation?.enabled === true && observation.directoryGuard !== undefined &&
|
|
375
375
|
observation.targetPath === state.canonicalCursor && observation.target !== undefined;
|
|
376
376
|
const kind = completeObservation
|
|
377
|
-
?
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
? "directory"
|
|
381
|
-
: toResolvedKind(observation.target.stat),
|
|
382
|
-
}
|
|
377
|
+
? isNativeDirectoryObservationGuard(observation.target)
|
|
378
|
+
? "directory"
|
|
379
|
+
: toResolvedKind(observation.target.stat)
|
|
383
380
|
: getPathKindSync(state.canonicalCursor, state.preserveFinalSymlink);
|
|
384
381
|
if (completeObservation && observationOutput) {
|
|
385
382
|
observationOutput.receipt = {
|
|
@@ -399,11 +396,11 @@ function getPathKindSync(absolutePath, preserveFinalSymlink) {
|
|
|
399
396
|
const stat = preserveFinalSymlink
|
|
400
397
|
? fs.lstatSync(operationPath)
|
|
401
398
|
: fs.statSync(operationPath);
|
|
402
|
-
return
|
|
399
|
+
return toResolvedKind(stat);
|
|
403
400
|
}
|
|
404
401
|
catch (error) {
|
|
405
402
|
if (isNotFoundPathError(error)) {
|
|
406
|
-
return
|
|
403
|
+
return "missing";
|
|
407
404
|
}
|
|
408
405
|
throw error;
|
|
409
406
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ResolvePathWithinRootParams } from "./path-scope-lexical.js";
|
|
1
2
|
import { type DirectoryResult } from "./root-directory.js";
|
|
2
3
|
export { resolvePathWithinRoot } from "./root-paths-lexical.js";
|
|
3
4
|
export { ensureDirectoryWithinRoot } from "./root-directory.js";
|
|
@@ -44,12 +45,7 @@ export type PathScope = {
|
|
|
44
45
|
mode?: number;
|
|
45
46
|
}): Promise<DirectoryResult>;
|
|
46
47
|
};
|
|
47
|
-
export declare function resolveWritablePathWithinRoot(params: {
|
|
48
|
-
rootDir: string;
|
|
49
|
-
requestedPath: string;
|
|
50
|
-
scopeLabel: string;
|
|
51
|
-
defaultFileName?: string;
|
|
52
|
-
}): Promise<{
|
|
48
|
+
export declare function resolveWritablePathWithinRoot(params: ResolvePathWithinRootParams): Promise<{
|
|
53
49
|
ok: true;
|
|
54
50
|
path: string;
|
|
55
51
|
} | {
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import type { BigIntStats } from "node:fs";
|
|
2
2
|
type ExactDirectoryIdentity = Readonly<Pick<BigIntStats, "dev" | "ino">>;
|
|
3
|
-
export type RemovalDirectoryAssertion = Readonly<{
|
|
3
|
+
export type RemovalDirectoryAssertion = ExactDirectoryIdentity & Readonly<{
|
|
4
4
|
path: string;
|
|
5
|
-
dev: bigint;
|
|
6
|
-
ino: bigint;
|
|
7
5
|
numericDev?: number;
|
|
8
6
|
numericIno?: number;
|
|
9
7
|
platform: NodeJS.Platform;
|
|
@@ -1,17 +1,21 @@
|
|
|
1
|
+
import type { DirectoryObservationGuard } from "./directory-guard.js";
|
|
2
|
+
import { type RootPathObservationReceipt } from "./root-path.js";
|
|
1
3
|
import type { RootDirectoryListing, RootDirectoryListingOptions } from "./root-directory-list.js";
|
|
2
4
|
import type { PathStat } from "./types.js";
|
|
3
|
-
export type RootWalkSymlinkPolicy = "skip" | "follow-within-root";
|
|
5
|
+
export type RootWalkSymlinkPolicy = "skip" | "follow-within-root" | "include";
|
|
6
|
+
type LegacyRootWalkSymlinkPolicy = Exclude<RootWalkSymlinkPolicy, "include">;
|
|
4
7
|
export type RootWalkLimitBehavior = "truncate" | "throw";
|
|
5
8
|
export type RootWalkDirectoryErrorBehavior = "throw" | "skip-and-report";
|
|
6
9
|
export type RootWalkEntryFilterResult = "include" | "skip" | "skip-subtree";
|
|
7
|
-
export type RootWalkDataEntryKind = "file" | "directory" | "other";
|
|
8
|
-
export type RootWalkEntryKind = RootWalkDataEntryKind | "directory-error" | "truncated";
|
|
9
|
-
|
|
10
|
+
export type RootWalkDataEntryKind<Policy extends RootWalkSymlinkPolicy = LegacyRootWalkSymlinkPolicy> = "file" | "directory" | "other" | ("include" extends Policy ? "symlink" : never);
|
|
11
|
+
export type RootWalkEntryKind<Policy extends RootWalkSymlinkPolicy = LegacyRootWalkSymlinkPolicy> = RootWalkDataEntryKind<Policy> | "directory-error" | "truncated";
|
|
12
|
+
type WalkEntryOfKind<Kind extends RootWalkDataEntryKind<RootWalkSymlinkPolicy>> = {
|
|
10
13
|
relativePath: string;
|
|
11
|
-
kind:
|
|
14
|
+
kind: Kind;
|
|
12
15
|
size: number;
|
|
13
16
|
};
|
|
14
|
-
export type
|
|
17
|
+
export type RootWalkDataEntry<Policy extends RootWalkSymlinkPolicy = LegacyRootWalkSymlinkPolicy> = WalkEntryOfKind<RootWalkDataEntryKind> | ("include" extends Policy ? WalkEntryOfKind<"symlink"> : never);
|
|
18
|
+
export type RootWalkEntry<Policy extends RootWalkSymlinkPolicy = LegacyRootWalkSymlinkPolicy> = RootWalkDataEntry<Policy> | {
|
|
15
19
|
relativePath: string;
|
|
16
20
|
kind: "truncated";
|
|
17
21
|
size: 0;
|
|
@@ -21,21 +25,24 @@ export type RootWalkEntry = RootWalkDataEntry | {
|
|
|
21
25
|
size: 0;
|
|
22
26
|
error: unknown;
|
|
23
27
|
};
|
|
24
|
-
export type RootWalkEntryFilter = (entry: RootWalkDataEntry) => RootWalkEntryFilterResult | Promise<RootWalkEntryFilterResult>;
|
|
25
|
-
export type RootWalkOptions = {
|
|
28
|
+
export type RootWalkEntryFilter<Policy extends RootWalkSymlinkPolicy = LegacyRootWalkSymlinkPolicy> = (entry: RootWalkDataEntry<Policy>) => RootWalkEntryFilterResult | Promise<RootWalkEntryFilterResult>;
|
|
29
|
+
export type RootWalkOptions<Policy extends RootWalkSymlinkPolicy = LegacyRootWalkSymlinkPolicy> = {
|
|
26
30
|
maxDepth?: number;
|
|
27
31
|
maxEntries?: number;
|
|
28
32
|
order?: "sorted" | "filesystem";
|
|
29
|
-
symlinkPolicy:
|
|
33
|
+
symlinkPolicy: Policy;
|
|
30
34
|
signal?: AbortSignal;
|
|
31
35
|
limitBehavior?: RootWalkLimitBehavior;
|
|
32
|
-
entryFilter?: RootWalkEntryFilter
|
|
36
|
+
entryFilter?: RootWalkEntryFilter<Policy>;
|
|
33
37
|
onDirectoryError?: RootWalkDirectoryErrorBehavior;
|
|
34
38
|
};
|
|
39
|
+
type AnyRootWalkOptions = RootWalkOptions | RootWalkOptions<"include"> | RootWalkOptions<RootWalkSymlinkPolicy>;
|
|
40
|
+
type AnyRootWalkEntry = RootWalkEntry<RootWalkSymlinkPolicy>;
|
|
35
41
|
type RootWalkCapability = {
|
|
36
42
|
rootReal: string;
|
|
43
|
+
observeRoot(): Promise<DirectoryObservationGuard | undefined>;
|
|
37
44
|
stat(relativePath: string): Promise<PathStat>;
|
|
38
|
-
list(relativePath: string, options: RootDirectoryListingOptions): Promise<RootDirectoryListing>;
|
|
45
|
+
list(relativePath: string, options: RootDirectoryListingOptions, receipt?: RootPathObservationReceipt): Promise<RootDirectoryListing>;
|
|
39
46
|
};
|
|
40
|
-
export declare function walkRoot(root: RootWalkCapability, relativePath: string, options:
|
|
47
|
+
export declare function walkRoot(root: RootWalkCapability, relativePath: string, options: AnyRootWalkOptions): AsyncGenerator<AnyRootWalkEntry>;
|
|
41
48
|
export {};
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { FsSafeError } from "./errors.js";
|
|
3
|
-
import {
|
|
3
|
+
import { expandRelativePathWithHome } from "./root-context.js";
|
|
4
|
+
import { resolveRootPath, resolveRootPathWithObservation, ROOT_PATH_ALIAS_POLICIES, RootPathObservationError, } from "./root-path.js";
|
|
4
5
|
import { createSuppressedError } from "./suppressed-error.js";
|
|
6
|
+
function filterEntry(options, entry) {
|
|
7
|
+
if (entry.kind === "symlink") {
|
|
8
|
+
return options.symlinkPolicy === "include" ? options.entryFilter?.(entry) ?? "include" : "skip";
|
|
9
|
+
}
|
|
10
|
+
return options.entryFilter?.(entry) ?? "include";
|
|
11
|
+
}
|
|
5
12
|
function validateBudget(name, value) {
|
|
6
13
|
if (value === undefined)
|
|
7
14
|
return Number.POSITIVE_INFINITY;
|
|
@@ -23,7 +30,7 @@ function limitEntry(relativePath) {
|
|
|
23
30
|
return { relativePath, kind: "truncated", size: 0 };
|
|
24
31
|
}
|
|
25
32
|
export async function* walkRoot(root, relativePath, options) {
|
|
26
|
-
if (!["skip", "follow-within-root"].includes(options.symlinkPolicy)) {
|
|
33
|
+
if (!["skip", "follow-within-root", "include"].includes(options.symlinkPolicy)) {
|
|
27
34
|
throw new TypeError(`invalid root walk symlink policy: ${String(options.symlinkPolicy)}`);
|
|
28
35
|
}
|
|
29
36
|
if (options.order !== undefined && !["sorted", "filesystem"].includes(options.order)) {
|
|
@@ -61,23 +68,50 @@ export async function* walkRoot(root, relativePath, options) {
|
|
|
61
68
|
throw error;
|
|
62
69
|
return { relativePath: directory, kind: "directory-error", size: 0, error };
|
|
63
70
|
};
|
|
64
|
-
async function* visit(directory, depth) {
|
|
71
|
+
async function* visit(directory, depth, admittedChildPath) {
|
|
65
72
|
options.signal?.throwIfAborted();
|
|
66
73
|
let listing;
|
|
74
|
+
let canonicalDirectory;
|
|
67
75
|
try {
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
|
|
76
|
+
const expandedDirectory = depth === 0 ? await expandRelativePathWithHome(directory) : directory;
|
|
77
|
+
const noFollowChildSymlinks = depth > 0 && options.symlinkPolicy !== "follow-within-root";
|
|
78
|
+
const includeChild = depth > 0 && options.symlinkPolicy === "include";
|
|
79
|
+
const resolution = {
|
|
80
|
+
absolutePath: admittedChildPath ?? path.resolve(root.rootReal, expandedDirectory),
|
|
71
81
|
rootPath: root.rootReal,
|
|
72
82
|
rootCanonicalPath: root.rootReal,
|
|
73
83
|
boundaryLabel: "root walk",
|
|
74
|
-
policy:
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
84
|
+
policy: noFollowChildSymlinks ? ROOT_PATH_ALIAS_POLICIES.unlinkTarget : undefined,
|
|
85
|
+
};
|
|
86
|
+
let receipt;
|
|
87
|
+
let resolvedDirectory;
|
|
88
|
+
if (includeChild) {
|
|
89
|
+
const rootGuard = await root.observeRoot();
|
|
90
|
+
if (!rootGuard) {
|
|
91
|
+
throw new FsSafeError("helper-unavailable", "root walk requires an exact directory observation");
|
|
92
|
+
}
|
|
93
|
+
const observed = await resolveRootPathWithObservation({
|
|
94
|
+
...resolution,
|
|
95
|
+
rootIdentity: rootGuard.identity,
|
|
96
|
+
}, { kind: "directory", rootGuard });
|
|
97
|
+
resolvedDirectory = observed.resolved;
|
|
98
|
+
receipt = observed.receipt;
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
resolvedDirectory = await resolveRootPath(resolution);
|
|
102
|
+
}
|
|
103
|
+
if (noFollowChildSymlinks && resolvedDirectory.kind === "symlink") {
|
|
104
|
+
if (options.symlinkPolicy === "skip")
|
|
105
|
+
return;
|
|
106
|
+
throw new FsSafeError("path-mismatch", `root walk directory became a symlink: ${directory}`);
|
|
107
|
+
}
|
|
78
108
|
if (!resolvedDirectory.exists || resolvedDirectory.kind !== "directory") {
|
|
79
109
|
throw new FsSafeError("not-file", `root walk path is not a directory: ${directory || "."}`);
|
|
80
110
|
}
|
|
111
|
+
if (includeChild && (!receipt || receipt.directoryGuard.realPath !== resolvedDirectory.canonicalPath)) {
|
|
112
|
+
throw new FsSafeError("path-mismatch", "root walk directory observation was not retained");
|
|
113
|
+
}
|
|
114
|
+
canonicalDirectory = resolvedDirectory.canonicalPath;
|
|
81
115
|
if (visitedDirectories.has(resolvedDirectory.canonicalPath)) {
|
|
82
116
|
return;
|
|
83
117
|
}
|
|
@@ -87,15 +121,18 @@ export async function* walkRoot(root, relativePath, options) {
|
|
|
87
121
|
.relative(root.rootReal, resolvedDirectory.canonicalPath)
|
|
88
122
|
.split(path.sep)
|
|
89
123
|
.join(path.posix.sep);
|
|
90
|
-
|
|
124
|
+
if (depth === 0 && expandedDirectory !== directory)
|
|
125
|
+
directory = listingDirectory;
|
|
126
|
+
// Resolved filesystem names are literal, not caller home-directory shorthand.
|
|
127
|
+
listing = await root.list(`./${listingDirectory}`, {
|
|
91
128
|
order: options.order ?? "sorted",
|
|
92
129
|
signal: options.signal,
|
|
93
130
|
snapshot: maxEntries === Number.POSITIVE_INFINITY,
|
|
94
131
|
admitEntry,
|
|
95
|
-
});
|
|
132
|
+
}, receipt);
|
|
96
133
|
}
|
|
97
134
|
catch (error) {
|
|
98
|
-
yield onDirectoryError(directory, error);
|
|
135
|
+
yield onDirectoryError(directory, error instanceof RootPathObservationError ? error.error : error);
|
|
99
136
|
return;
|
|
100
137
|
}
|
|
101
138
|
// A thrown undefined still needs to be retained if closing also fails.
|
|
@@ -126,10 +163,9 @@ export async function* walkRoot(root, relativePath, options) {
|
|
|
126
163
|
const entry = next.entry;
|
|
127
164
|
let kind = entryKind(entry);
|
|
128
165
|
let size = entry.size;
|
|
129
|
-
if (kind === "symlink")
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
166
|
+
if (kind === "symlink" && options.symlinkPolicy === "skip")
|
|
167
|
+
continue;
|
|
168
|
+
if (kind === "symlink" && options.symlinkPolicy === "follow-within-root") {
|
|
133
169
|
const resolved = await resolveRootPath({
|
|
134
170
|
absolutePath: path.resolve(root.rootReal, child),
|
|
135
171
|
rootPath: root.rootReal,
|
|
@@ -139,12 +175,12 @@ export async function* walkRoot(root, relativePath, options) {
|
|
|
139
175
|
if (!resolved.exists) {
|
|
140
176
|
continue;
|
|
141
177
|
}
|
|
142
|
-
const target = await root.stat(path.relative(root.rootReal, resolved.canonicalPath));
|
|
178
|
+
const target = await root.stat(`./${path.relative(root.rootReal, resolved.canonicalPath)}`);
|
|
143
179
|
kind = target.isDirectory ? "directory" : target.isFile ? "file" : "other";
|
|
144
180
|
size = target.size;
|
|
145
181
|
}
|
|
146
182
|
const walkEntry = { relativePath: child, kind, size };
|
|
147
|
-
let filterResult = options
|
|
183
|
+
let filterResult = filterEntry(options, walkEntry);
|
|
148
184
|
if (typeof filterResult !== "string") {
|
|
149
185
|
filterResult = (await filterResult) ?? "include";
|
|
150
186
|
options.signal?.throwIfAborted();
|
|
@@ -172,7 +208,7 @@ export async function* walkRoot(root, relativePath, options) {
|
|
|
172
208
|
yield onLimit(child);
|
|
173
209
|
return;
|
|
174
210
|
}
|
|
175
|
-
yield* visit(child, depth + 1);
|
|
211
|
+
yield* visit(child, depth + 1, options.symlinkPolicy === "include" ? path.join(canonicalDirectory, name) : undefined);
|
|
176
212
|
if (truncated)
|
|
177
213
|
return;
|
|
178
214
|
}
|
|
@@ -14,7 +14,7 @@ import { mutationSymlinkResolution, } from "./root-symlink-policy.js";
|
|
|
14
14
|
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
15
15
|
import { realpathSync } from "./realpath.js";
|
|
16
16
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
17
|
-
import { assertPreparedRootWriteParentCurrent, } from "./root-write-complete-parent.js";
|
|
17
|
+
import { assertPreparedRootWriteParentCurrent, writeSelectionChanged, } from "./root-write-complete-parent.js";
|
|
18
18
|
export function createRootWriteSelectionForFd(selection, fd) {
|
|
19
19
|
const stat = inspectFileIdentitySync(() => fsSync.fstatSync(fd, { bigint: true }));
|
|
20
20
|
return Object.freeze({
|
|
@@ -22,9 +22,6 @@ export function createRootWriteSelectionForFd(selection, fd) {
|
|
|
22
22
|
identity: Object.freeze({ dev: stat.dev, ino: stat.ino }),
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
|
-
function writeSelectionChanged(cause) {
|
|
26
|
-
return new FsSafeError("path-mismatch", "write target changed during operation", errorCauseOptions(cause));
|
|
27
|
-
}
|
|
28
25
|
function inspectRegularSelectionPath(pathname, expected, follow) {
|
|
29
26
|
let observed;
|
|
30
27
|
try {
|
|
@@ -204,7 +201,6 @@ export async function resolveGuardedWriteTargetInRoot(root, params) {
|
|
|
204
201
|
const relativeParent = path.relative(resolvedPath.rootReal, path.dirname(resolvedPath.resolved));
|
|
205
202
|
const prepared = await preparePinnedWriteMutationAdmission({
|
|
206
203
|
rootReal: resolvedPath.rootReal,
|
|
207
|
-
rootWithSep: resolvedPath.rootWithSep,
|
|
208
204
|
rootIdentity: root.rootIdentity,
|
|
209
205
|
resolvedTargetPath: resolvedPath.resolved,
|
|
210
206
|
originalPath: params.relativePath,
|
|
@@ -291,7 +287,6 @@ export async function resolvePinnedWriteTargetInRoot(root, relativePath, request
|
|
|
291
287
|
if (policy) {
|
|
292
288
|
({ relativeParentPath, mutationAdmission } = await preparePinnedWriteMutationAdmission({
|
|
293
289
|
rootReal,
|
|
294
|
-
rootWithSep,
|
|
295
290
|
rootIdentity: root.rootIdentity,
|
|
296
291
|
resolvedTargetPath: resolved,
|
|
297
292
|
originalPath: relativePath,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { BigIntStats } from "node:fs";
|
|
2
2
|
import { type AsyncDirectoryGuard } from "./directory-guard.js";
|
|
3
|
+
import { FsSafeError } from "./errors.js";
|
|
3
4
|
import type { RootContext } from "./root-context.js";
|
|
4
5
|
import type { GuardedRootWriteTarget } from "./root-write-admission.js";
|
|
5
6
|
type PreparedWriteTargetObservation = Readonly<{
|
|
@@ -36,6 +37,7 @@ export type SharedRootWriteTarget = Readonly<{
|
|
|
36
37
|
mutationAdmission: GuardedRootWriteTarget["mutationAdmission"];
|
|
37
38
|
preparedParent?: PreparedRootWriteParent;
|
|
38
39
|
}>;
|
|
40
|
+
export declare function writeSelectionChanged(cause?: unknown): FsSafeError;
|
|
39
41
|
export declare function assertPreparedRootWriteParentCurrent(prepared: PreparedRootWriteParent, verifyTarget?: boolean): void;
|
|
40
42
|
export declare function prepareSharedRootWriteTarget(root: RootContext, params: SharedRootWriteTargetParams): Promise<SharedRootWriteTarget>;
|
|
41
43
|
export {};
|
|
@@ -12,7 +12,7 @@ import { errorCauseOptions } from "./root-errors.js";
|
|
|
12
12
|
import { canReuseParentWithMutationAssertion } from "./root-write-lock-binding.js";
|
|
13
13
|
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
14
14
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
15
|
-
function writeSelectionChanged(cause) {
|
|
15
|
+
export function writeSelectionChanged(cause) {
|
|
16
16
|
return new FsSafeError("path-mismatch", "write target changed during operation", errorCauseOptions(cause));
|
|
17
17
|
}
|
|
18
18
|
function ordinarySharedWriteRoute(root, relativePath, operationTargetPath) {
|
|
@@ -7,5 +7,6 @@ export declare function assertNoDriveRelativePathSegments(value: string, label:
|
|
|
7
7
|
export declare function trimHyphenEdges(value: string): string;
|
|
8
8
|
export declare function isSafePathSegment(segment: string, options?: SafePathSegmentOptions): boolean;
|
|
9
9
|
export declare function assertSafePathSegment(segment: string, options?: SafePathSegmentOptions): string;
|
|
10
|
+
export declare function normalizeSafePathSegment(value: string): string;
|
|
10
11
|
export declare function sanitizeSafePathSegment(value: string): string | undefined;
|
|
11
12
|
export declare function assertSafePathPrefix(prefix: string, options?: SafePathSegmentOptions): string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isWindowsReservedDeviceName } from "./device-path.js";
|
|
1
2
|
import { FsSafeError } from "./errors.js";
|
|
2
3
|
const SAFE_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9_-][A-Za-z0-9._-]*$/;
|
|
3
4
|
const SAFE_DOT_PREFIX_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9._-]+$/;
|
|
@@ -34,6 +35,8 @@ export function isSafePathSegment(segment, options = {}) {
|
|
|
34
35
|
!segment.includes("/") &&
|
|
35
36
|
!segment.includes("\\") &&
|
|
36
37
|
!segment.includes("\0") &&
|
|
38
|
+
// Segments are portable identifiers: CON.json and CON.<pid>.tmp name devices on Windows.
|
|
39
|
+
!isWindowsReservedDeviceName(segment) &&
|
|
37
40
|
(options.allowDotPrefix === true || !segment.startsWith(".")) &&
|
|
38
41
|
(options.allowDotPrefix === true
|
|
39
42
|
? SAFE_DOT_PREFIX_PATH_SEGMENT_PATTERN.test(segment)
|
|
@@ -47,13 +50,16 @@ export function assertSafePathSegment(segment, options = {}) {
|
|
|
47
50
|
}
|
|
48
51
|
return segment;
|
|
49
52
|
}
|
|
50
|
-
export function
|
|
53
|
+
export function normalizeSafePathSegment(value) {
|
|
51
54
|
const sanitized = value
|
|
52
55
|
.trim()
|
|
53
56
|
.replace(/[\\/]+/g, "-")
|
|
54
57
|
.replace(/\0/g, "")
|
|
55
58
|
.replace(/[^A-Za-z0-9._-]+/g, "-");
|
|
56
|
-
|
|
59
|
+
return trimHyphenEdges(sanitized);
|
|
60
|
+
}
|
|
61
|
+
export function sanitizeSafePathSegment(value) {
|
|
62
|
+
const trimmed = normalizeSafePathSegment(value);
|
|
57
63
|
return isSafePathSegment(trimmed, { allowDotPrefix: true }) ? trimmed : undefined;
|
|
58
64
|
}
|
|
59
65
|
export function assertSafePathPrefix(prefix, options = {}) {
|
|
@@ -13,7 +13,11 @@ type SecretFileWriteParams = {
|
|
|
13
13
|
dirMode?: number;
|
|
14
14
|
durable?: boolean;
|
|
15
15
|
};
|
|
16
|
-
|
|
16
|
+
type SecretFileCreateParams = Omit<SecretFileWriteParams, "durable"> & {
|
|
17
|
+
/** "file" requires file synchronization; directory synchronization remains best effort. */
|
|
18
|
+
durable?: boolean | "file";
|
|
19
|
+
};
|
|
20
|
+
export declare function prepareSecretFileWrite(params: Pick<SecretFileWriteParams, "rootDir" | "filePath" | "mode" | "dirMode">): Promise<{
|
|
17
21
|
mode: number;
|
|
18
22
|
rootGuard: AsyncDirectoryGuard<BigIntStats>;
|
|
19
23
|
parentGuard: AsyncDirectoryGuard<BigIntStats>;
|
|
@@ -21,5 +25,5 @@ export declare function prepareSecretFileWrite(params: Omit<SecretFileWriteParam
|
|
|
21
25
|
finalFilePath: string;
|
|
22
26
|
}>;
|
|
23
27
|
export declare function writeSecretFileAtomic(params: SecretFileWriteParams): Promise<void>;
|
|
24
|
-
export declare function createSecretFileAtomic(params:
|
|
28
|
+
export declare function createSecretFileAtomic(params: SecretFileCreateParams): Promise<void>;
|
|
25
29
|
export {};
|
|
@@ -5,8 +5,7 @@ import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
|
|
|
5
5
|
import { canonicalPathFromExistingAncestor } from "./absolute-path.js";
|
|
6
6
|
import { readFileDescriptorBoundedSync } from "./bounded-read.js";
|
|
7
7
|
import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard, inspectDirectoryIdentity } from "./directory-guard.js";
|
|
8
|
-
import { pinNodeDirectoryForMode } from "./directory-mode-node.js";
|
|
9
|
-
import { assertOwnedDirectory } from "./directory-mode-owner.js";
|
|
8
|
+
import { pinNodeDirectoryForMode, assertOwnedDirectory } from "./directory-mode-node.js";
|
|
10
9
|
import { FsSafeError } from "./errors.js";
|
|
11
10
|
import { openPinnedFileSync } from "./pinned-open.js";
|
|
12
11
|
import { runPinnedWriteHelper } from "./pinned-write.js";
|
|
@@ -199,7 +198,11 @@ async function ensurePrivateDirectory(rootDir, targetDir, mode) {
|
|
|
199
198
|
}
|
|
200
199
|
return { rootGuard, parentGuard: targetGuard };
|
|
201
200
|
}
|
|
202
|
-
function snapshotSecretFileWriteParams(params
|
|
201
|
+
function snapshotSecretFileWriteParams(params) {
|
|
202
|
+
const rootDir = params.rootDir;
|
|
203
|
+
const filePath = params.filePath;
|
|
204
|
+
assertNoWindowsPathAlias(rootDir, "filesystem", "private secret root uses a Windows filesystem namespace alias");
|
|
205
|
+
assertNoWindowsPathAlias(filePath, "filesystem", "private secret path uses a Windows filesystem namespace alias");
|
|
203
206
|
return {
|
|
204
207
|
rootDir,
|
|
205
208
|
filePath,
|
|
@@ -282,6 +285,7 @@ async function materializeSecretFileAtomic(params, createOnly) {
|
|
|
282
285
|
mode,
|
|
283
286
|
verifyPosixMode: true,
|
|
284
287
|
sync: params.durable !== false,
|
|
288
|
+
strictFileSync: createOnly && params.durable === "file",
|
|
285
289
|
overwrite: !createOnly,
|
|
286
290
|
input: { kind: "buffer", data: typeof params.content === "string" ? params.content : Buffer.from(params.content) },
|
|
287
291
|
rootIdentity: { dev: parentGuard.stat.dev, ino: parentGuard.stat.ino },
|
|
@@ -305,31 +309,23 @@ async function materializeSecretFileAtomic(params, createOnly) {
|
|
|
305
309
|
});
|
|
306
310
|
}
|
|
307
311
|
export async function writeSecretFileAtomic(params) {
|
|
308
|
-
const
|
|
309
|
-
const
|
|
310
|
-
assertNoWindowsPathAlias(rootDir, "filesystem", "private secret root uses a Windows filesystem namespace alias");
|
|
311
|
-
assertNoWindowsPathAlias(filePath, "filesystem", "private secret path uses a Windows filesystem namespace alias");
|
|
312
|
-
const ownedParams = snapshotSecretFileWriteParams(params, rootDir, filePath);
|
|
313
|
-
const canonicalPath = await secretFileWriteQueueKey(filePath);
|
|
312
|
+
const ownedParams = snapshotSecretFileWriteParams(params);
|
|
313
|
+
const canonicalPath = await secretFileWriteQueueKey(ownedParams.filePath);
|
|
314
314
|
await serializePathWrite(canonicalPath, async () => {
|
|
315
315
|
await materializeSecretFileAtomic(ownedParams, false);
|
|
316
316
|
});
|
|
317
317
|
}
|
|
318
318
|
export async function createSecretFileAtomic(params) {
|
|
319
319
|
try {
|
|
320
|
-
const
|
|
321
|
-
const
|
|
322
|
-
assertNoWindowsPathAlias(rootDir, "filesystem", "private secret root uses a Windows filesystem namespace alias");
|
|
323
|
-
assertNoWindowsPathAlias(filePath, "filesystem", "private secret path uses a Windows filesystem namespace alias");
|
|
324
|
-
const ownedParams = snapshotSecretFileWriteParams(params, rootDir, filePath);
|
|
325
|
-
const canonicalPath = await secretFileWriteQueueKey(filePath);
|
|
320
|
+
const ownedParams = snapshotSecretFileWriteParams(params);
|
|
321
|
+
const canonicalPath = await secretFileWriteQueueKey(ownedParams.filePath);
|
|
326
322
|
await serializePathWrite(canonicalPath, async () => {
|
|
327
323
|
await materializeSecretFileAtomic(ownedParams, true);
|
|
328
324
|
});
|
|
329
325
|
}
|
|
330
326
|
catch (error) {
|
|
331
327
|
if ((error instanceof FsSafeError && error.code === "already-exists") ||
|
|
332
|
-
error
|
|
328
|
+
error?.code === "EEXIST") {
|
|
333
329
|
throw new FsSafeError("secret-exists", "Private secret file already exists.", { cause: error });
|
|
334
330
|
}
|
|
335
331
|
throw error;
|
|
@@ -7,7 +7,7 @@ import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
|
7
7
|
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
8
8
|
export const DEFAULT_SECRET_FILE_MAX_BYTES = 16 * 1024;
|
|
9
9
|
export function secretPathErrorCode(error) {
|
|
10
|
-
const code = error
|
|
10
|
+
const code = error?.code;
|
|
11
11
|
return code === "ENOENT" || code === "ENOTDIR" ? "not-found" : "invalid-path";
|
|
12
12
|
}
|
|
13
13
|
export function secretReadError(code, action, label, resolvedPath, error) {
|
|
@@ -1,29 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { recordFileObservationFailure } from "./file-observation.js";
|
|
1
|
+
import { fileIdentityMismatchError } from "./strict-file-identity.js";
|
|
3
2
|
import { getNativeBinding } from "./native.js";
|
|
4
3
|
import { getFsSafeNativeConfig } from "./native-config.js";
|
|
5
4
|
import { warnNativeFallback } from "./native-fallback-warning.js";
|
|
6
5
|
import { inspectWindowsDescriptorCommand } from "./windows-security-command.js";
|
|
7
|
-
import { validateSecureWindowsSecurityFacts } from "./windows-security-facts.js";
|
|
6
|
+
import { unverified as permissionUnverified, validateSecureWindowsSecurityFacts } from "./windows-security-facts.js";
|
|
8
7
|
const IDENTITY_RE = /^([0-9a-f]{8}):([0-9a-f]{16})$/;
|
|
9
8
|
const TRUSTED_OWNER_CLASSES = new Set(["current-user", "system", "administrators"]);
|
|
10
|
-
function permissionUnverified(message, cause) {
|
|
11
|
-
throw new FsSafeError("permission-unverified", message, cause === undefined ? {} : { cause });
|
|
12
|
-
}
|
|
13
|
-
function identityMismatch() {
|
|
14
|
-
const error = new FsSafeError("path-mismatch", "file identity changed or could not be verified");
|
|
15
|
-
recordFileObservationFailure(error, "identity");
|
|
16
|
-
throw error;
|
|
17
|
-
}
|
|
18
9
|
function isRecord(value) {
|
|
19
10
|
return typeof value === "object" && value !== null;
|
|
20
11
|
}
|
|
21
12
|
function parseIdentity(value) {
|
|
22
13
|
if (typeof value !== "string")
|
|
23
|
-
|
|
14
|
+
throw fileIdentityMismatchError();
|
|
24
15
|
const match = IDENTITY_RE.exec(value);
|
|
25
16
|
if (!match)
|
|
26
|
-
|
|
17
|
+
throw fileIdentityMismatchError();
|
|
27
18
|
return {
|
|
28
19
|
dev: BigInt(`0x${match[1]}`),
|
|
29
20
|
ino: BigInt(`0x${match[2]}`),
|
|
@@ -34,7 +25,7 @@ function inspectDescriptorResult(params, result, mechanism) {
|
|
|
34
25
|
permissionUnverified("Windows descriptor ACL facts were malformed");
|
|
35
26
|
const observed = parseIdentity(result.identity);
|
|
36
27
|
if (observed.dev !== params.identity.dev || observed.ino !== params.identity.ino) {
|
|
37
|
-
|
|
28
|
+
throw fileIdentityMismatchError();
|
|
38
29
|
}
|
|
39
30
|
const facts = validateSecureWindowsSecurityFacts(result.security);
|
|
40
31
|
return {
|