@openclaw/feishu 2026.9.8 → 2026.10.1-beta.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/dist/.setup/{channel-DV4hM2Uf.mjs → channel-CX62T71S.mjs} +146 -245
- package/dist/.setup/{channel.runtime-CXQcH5zZ.mjs → channel.runtime-COiaq-Rv.mjs} +6 -12
- package/dist/.setup/{chat-D8MlFAZi.mjs → chat-BdyvImsY.mjs} +6 -9
- package/dist/.setup/{client-BYG-_IZl.mjs → client-BsBVWJzN.mjs} +1 -1
- package/dist/.setup/{doctor-contract-3XC0vbIw.mjs → doctor-contract-B1B9ogdd.mjs} +212 -27
- package/dist/.setup/{monitor-D_rsHBZ6.mjs → monitor-7DvwUs4Y.mjs} +5 -6
- package/dist/.setup/{monitor.account-DvvY7i6k.mjs → monitor.account-7xVu7XWY.mjs} +190 -393
- package/dist/.setup/{probe-_ViqSi0j.mjs → probe-B9o7iey6.mjs} +13 -39
- package/dist/.setup/{reply-delivery-result-COAjaukc.mjs → reply-delivery-result-Bt-BWBEA.mjs} +151 -297
- package/dist/.setup/{setup-api-C4S5I3ac.mjs → setup-api-DmzX9S01.mjs} +1 -1
- package/dist/.setup/{subagent-hooks-BaFoMu8W.mjs → subagent-hooks-CUXg0pC-.mjs} +5 -11
- package/dist/.setup/{thread-bindings-EdLXPXzw.mjs → thread-bindings-jW2x5VuN.mjs} +23 -41
- package/dist/api.js +234 -297
- package/dist/channel-plugin-api.js +1 -1
- package/dist/doctor-contract-api.js +2 -2
- package/dist/session-binding-contract-api.js +1 -1
- package/dist/setup-api.js +1 -1
- package/dist/setup-entry.js +1 -1
- package/dist/subagent-hooks-api.js +1 -1
- package/node_modules/@openclaw/fs-safe/CHANGELOG.md +95 -0
- package/node_modules/@openclaw/fs-safe/README.md +70 -379
- package/node_modules/@openclaw/fs-safe/dist/absolute-path.js +35 -51
- package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/advanced.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-input.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-parser.wasm +0 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-staging.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/archive-staging.js +9 -10
- package/node_modules/@openclaw/fs-safe/dist/archive.js +2 -5
- package/node_modules/@openclaw/fs-safe/dist/atomic-io.d.ts +51 -0
- package/node_modules/@openclaw/fs-safe/dist/atomic-io.js +242 -0
- package/node_modules/@openclaw/fs-safe/dist/config.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/config.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/copy-file-input.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/copy-file-input.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/copy-tree-portable.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/create.js +12 -5
- package/node_modules/@openclaw/fs-safe/dist/directory-guard.d.ts +11 -0
- package/node_modules/@openclaw/fs-safe/dist/directory-guard.js +4 -7
- package/node_modules/@openclaw/fs-safe/dist/entry-publication-types.d.ts +55 -0
- package/node_modules/@openclaw/fs-safe/dist/entry-publication-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/entry-publication.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/entry-publication.js +262 -0
- package/node_modules/@openclaw/fs-safe/dist/exclusive-create.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/exclusive-create.js +22 -0
- package/node_modules/@openclaw/fs-safe/dist/file-cleanup.d.ts +1 -6
- package/node_modules/@openclaw/fs-safe/dist/file-cleanup.js +3 -6
- package/node_modules/@openclaw/fs-safe/dist/file-identity.js +3 -6
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-io.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-mutation.js +9 -0
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root.js +4 -6
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-stale-admission.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync.js +8 -0
- package/node_modules/@openclaw/fs-safe/dist/file-store-boundary.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/file-store-sync-write.js +20 -18
- package/node_modules/@openclaw/fs-safe/dist/file-store.js +4 -10
- package/node_modules/@openclaw/fs-safe/dist/fs.d.ts +2 -10
- package/node_modules/@openclaw/fs-safe/dist/fs.js +2 -10
- package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.d.ts +3 -4
- package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.js +6 -18
- package/node_modules/@openclaw/fs-safe/dist/guest-dispatch-python.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +0 -2
- package/node_modules/@openclaw/fs-safe/dist/guest.js +3 -9
- package/node_modules/@openclaw/fs-safe/dist/index.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/index.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-directory.js +1 -5
- package/node_modules/@openclaw/fs-safe/dist/json.js +19 -37
- package/node_modules/@openclaw/fs-safe/dist/move-path.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/mutation-authority.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/mutation-authority.js +9 -1
- package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +47 -2
- package/node_modules/@openclaw/fs-safe/dist/native-config.d.ts +1 -9
- package/node_modules/@openclaw/fs-safe/dist/native-config.js +6 -40
- package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.js +5 -2
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +11 -316
- package/node_modules/@openclaw/fs-safe/dist/native-policy-parent-windows.d.ts +7 -7
- package/node_modules/@openclaw/fs-safe/dist/native-policy-parent-windows.js +28 -175
- package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.d.ts +15 -0
- package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.js +418 -0
- package/node_modules/@openclaw/fs-safe/dist/native-rename-outcome.js +9 -4
- package/node_modules/@openclaw/fs-safe/dist/native-staged-file.js +17 -13
- package/node_modules/@openclaw/fs-safe/dist/native-staged-symlink.js +6 -24
- package/node_modules/@openclaw/fs-safe/dist/native.js +5 -1
- package/node_modules/@openclaw/fs-safe/dist/path-case.js +10 -9
- package/node_modules/@openclaw/fs-safe/dist/path-scope-lexical.js +4 -1
- package/node_modules/@openclaw/fs-safe/dist/path-segment-route.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/path-segment-route.js +3 -0
- package/node_modules/@openclaw/fs-safe/dist/path.js +4 -1
- package/node_modules/@openclaw/fs-safe/dist/permissions-windows.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-admission.js +1 -3
- package/node_modules/@openclaw/fs-safe/dist/pinned-write-staged.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/pinned-write.js +9 -10
- package/node_modules/@openclaw/fs-safe/dist/private-producer-handoff.js +3 -6
- package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/publish-file.js +4 -3
- package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.d.ts +3 -4
- package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.js +18 -32
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.d.ts +5 -17
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.js +92 -230
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-source.d.ts +4 -15
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-source.js +22 -63
- package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.d.ts +9 -34
- package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +78 -83
- package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.d.ts +18 -20
- package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.js +64 -98
- package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.d.ts +16 -33
- package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.js +61 -200
- package/node_modules/@openclaw/fs-safe/dist/replace-file-types.d.ts +1 -6
- package/node_modules/@openclaw/fs-safe/dist/replace-file.js +76 -216
- package/node_modules/@openclaw/fs-safe/dist/root-boundary.js +14 -24
- package/node_modules/@openclaw/fs-safe/dist/root-context.d.ts +1 -4
- package/node_modules/@openclaw/fs-safe/dist/root-context.js +4 -19
- package/node_modules/@openclaw/fs-safe/dist/root-create-native.d.ts +52 -0
- package/node_modules/@openclaw/fs-safe/dist/root-create-native.js +350 -0
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.d.ts +35 -0
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.d.ts +2 -27
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +31 -28
- package/node_modules/@openclaw/fs-safe/dist/root-directory.js +1 -4
- package/node_modules/@openclaw/fs-safe/dist/root-errors.js +34 -0
- package/node_modules/@openclaw/fs-safe/dist/root-file.js +9 -21
- package/node_modules/@openclaw/fs-safe/dist/root-impl.js +105 -37
- package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.d.ts +7 -2
- package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +67 -38
- package/node_modules/@openclaw/fs-safe/dist/root-observed-path.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/root-observed-path.js +0 -2
- package/node_modules/@openclaw/fs-safe/dist/root-options.d.ts +2 -3
- package/node_modules/@openclaw/fs-safe/dist/root-path-existing.js +3 -1
- package/node_modules/@openclaw/fs-safe/dist/root-path-stat.js +68 -85
- package/node_modules/@openclaw/fs-safe/dist/root-path.js +6 -1
- package/node_modules/@openclaw/fs-safe/dist/root-paths-lexical.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/root-paths-lexical.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/root-paths.js +4 -9
- package/node_modules/@openclaw/fs-safe/dist/root-remove-native.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/root-remove-native.js +311 -0
- package/node_modules/@openclaw/fs-safe/dist/root-remove.d.ts +11 -0
- package/node_modules/@openclaw/fs-safe/dist/root-remove.js +3 -1
- package/node_modules/@openclaw/fs-safe/dist/root-walk.d.ts +2 -2
- package/node_modules/@openclaw/fs-safe/dist/root-walk.js +1 -3
- package/node_modules/@openclaw/fs-safe/dist/root-write-admission.d.ts +3 -3
- package/node_modules/@openclaw/fs-safe/dist/root-write-admission.js +8 -10
- package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.js +0 -4
- package/node_modules/@openclaw/fs-safe/dist/secret-file.js +12 -9
- package/node_modules/@openclaw/fs-safe/dist/secure-temp-dir.d.ts +0 -2
- package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/sibling-temp.js +7 -4
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js +13 -6
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.d.ts +0 -3
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js +5 -23
- package/node_modules/@openclaw/fs-safe/dist/staged-file-settlement.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/staged-file-settlement.js +11 -6
- package/node_modules/@openclaw/fs-safe/dist/temp-cleanup.js +1 -8
- 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 +2 -9
- package/node_modules/@openclaw/fs-safe/dist/text-atomic.d.ts +1 -6
- package/node_modules/@openclaw/fs-safe/dist/trash.js +6 -2
- package/node_modules/@openclaw/fs-safe/dist/walk.js +100 -46
- package/node_modules/@openclaw/fs-safe/dist/watch-hints.d.ts +7 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-hints.js +235 -19
- package/node_modules/@openclaw/fs-safe/dist/watch-native.d.ts +21 -2
- package/node_modules/@openclaw/fs-safe/dist/watch-native.js +21 -3
- package/node_modules/@openclaw/fs-safe/dist/watch-rescan.d.ts +6 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-rescan.js +111 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-scan.d.ts +13 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-scan.js +27 -4
- package/node_modules/@openclaw/fs-safe/dist/watch-stream.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/watch.js +131 -40
- package/node_modules/@openclaw/fs-safe/dist/windows-path-alias.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-path-alias.js +24 -6
- package/node_modules/@openclaw/fs-safe/dist/windows-path-syntax.d.ts +13 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-path-syntax.js +51 -0
- package/node_modules/@openclaw/fs-safe/docs/advanced.md +4 -0
- package/node_modules/@openclaw/fs-safe/docs/atomic.md +4 -1
- package/node_modules/@openclaw/fs-safe/docs/config.md +6 -14
- package/node_modules/@openclaw/fs-safe/docs/contributing.md +4 -14
- package/node_modules/@openclaw/fs-safe/docs/durability.md +6 -29
- package/node_modules/@openclaw/fs-safe/docs/entry-publication.md +146 -0
- package/node_modules/@openclaw/fs-safe/docs/errors.md +16 -0
- package/node_modules/@openclaw/fs-safe/docs/file-store.md +28 -5
- package/node_modules/@openclaw/fs-safe/docs/index.md +2 -31
- package/node_modules/@openclaw/fs-safe/docs/install.md +10 -31
- package/node_modules/@openclaw/fs-safe/docs/json.md +1 -1
- package/node_modules/@openclaw/fs-safe/docs/local-roots.md +0 -1
- package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.5.md +12 -12
- package/node_modules/@openclaw/fs-safe/docs/native-helper.md +35 -96
- package/node_modules/@openclaw/fs-safe/docs/native.md +22 -20
- package/node_modules/@openclaw/fs-safe/docs/path.md +1 -1
- package/node_modules/@openclaw/fs-safe/docs/permissions.md +1 -2
- package/node_modules/@openclaw/fs-safe/docs/public-api.md +1 -2
- package/node_modules/@openclaw/fs-safe/docs/reading.md +1 -2
- package/node_modules/@openclaw/fs-safe/docs/root.md +26 -184
- package/node_modules/@openclaw/fs-safe/docs/secret-file.md +1 -1
- package/node_modules/@openclaw/fs-safe/docs/security-model.md +115 -3
- package/node_modules/@openclaw/fs-safe/docs/store.md +2 -2
- package/node_modules/@openclaw/fs-safe/docs/temp.md +29 -65
- package/node_modules/@openclaw/fs-safe/docs/testing.md +90 -58
- package/node_modules/@openclaw/fs-safe/docs/types.md +3 -16
- package/node_modules/@openclaw/fs-safe/docs/walk.md +33 -5
- package/node_modules/@openclaw/fs-safe/docs/watch.md +96 -14
- package/node_modules/@openclaw/fs-safe/docs/writing.md +172 -16
- package/node_modules/@openclaw/fs-safe/package.json +10 -10
- 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/package.json +5 -5
- package/skills/feishu-wiki/SKILL.md +1 -1
- package/dist/.setup/accounts-wRqItHug.mjs +0 -206
- package/node_modules/@openclaw/fs-safe/dist/watch-alias.d.ts +0 -6
- package/node_modules/@openclaw/fs-safe/dist/watch-alias.js +0 -88
- package/node_modules/@openclaw/fs-safe/docs/mutation-policy-proof.md +0 -69
- package/node_modules/@openclaw/fs-safe/docs/private-file-store.md +0 -68
- package/node_modules/@openclaw/fs-safe/docs/test-hooks.md +0 -110
|
@@ -36,7 +36,7 @@ export type RetainedRootWriteSelection = RootWritePathSelection & Readonly<{
|
|
|
36
36
|
}>;
|
|
37
37
|
export declare function createRootWriteSelectionForFd(selection: RootWritePathSelection, fd: number): RetainedRootWriteSelection;
|
|
38
38
|
export declare function assertRootWritePathSelectionSync(root: RootContext, selection: RootWritePathSelection): void;
|
|
39
|
-
export declare function assertRootWriteSelectionSync(root: RootContext, selection: RetainedRootWriteSelection,
|
|
39
|
+
export declare function assertRootWriteSelectionSync(root: RootContext, selection: RetainedRootWriteSelection, fd?: number): void;
|
|
40
40
|
export declare function prepareRootWritePathSelection(params: Omit<RootWritePathSelection, "parentGuard"> & Readonly<{
|
|
41
41
|
preparedParent?: PreparedRootWriteParent;
|
|
42
42
|
}>): Promise<RootWritePathSelection>;
|
|
@@ -49,8 +49,8 @@ export declare function refreshRootWritePathSelection(selection: RootWritePathSe
|
|
|
49
49
|
* Re-authorize a retained shared-JavaScript destination without recapturing
|
|
50
50
|
* either its pathname binding or its exact parent fence.
|
|
51
51
|
*/
|
|
52
|
-
export declare function refreshRootWriteSelection(root: RootContext, selection: RetainedRootWriteSelection,
|
|
53
|
-
export declare function refreshRetainedRootWriteAdmission(root: RootContext, selection: RetainedRootWriteSelection,
|
|
52
|
+
export declare function refreshRootWriteSelection(root: RootContext, selection: RetainedRootWriteSelection, fd?: number): Promise<void>;
|
|
53
|
+
export declare function refreshRetainedRootWriteAdmission(root: RootContext, selection: RetainedRootWriteSelection, fd?: number): Promise<void>;
|
|
54
54
|
type GuardedWritePathOptions = {
|
|
55
55
|
relativePath: string;
|
|
56
56
|
denyMutations?: DenyMutationPolicy;
|
|
@@ -92,9 +92,8 @@ export function assertRootWritePathSelectionSync(root, selection) {
|
|
|
92
92
|
}
|
|
93
93
|
assertSyncDirectoryGuard(selection.parentGuard);
|
|
94
94
|
}
|
|
95
|
-
export function assertRootWriteSelectionSync(root, selection,
|
|
96
|
-
|
|
97
|
-
inspectSelectionBindingSync(root, selection, fd);
|
|
95
|
+
export function assertRootWriteSelectionSync(root, selection, fd) {
|
|
96
|
+
inspectSelectionBindingSync(root, selection, fd);
|
|
98
97
|
assertSyncDirectoryGuard(selection.parentGuard);
|
|
99
98
|
}
|
|
100
99
|
async function authorizeRootWritePathSelection(selection, afterAwait) {
|
|
@@ -154,15 +153,14 @@ export async function refreshRootWritePathSelection(selection) {
|
|
|
154
153
|
* Re-authorize a retained shared-JavaScript destination without recapturing
|
|
155
154
|
* either its pathname binding or its exact parent fence.
|
|
156
155
|
*/
|
|
157
|
-
export async function refreshRootWriteSelection(root, selection,
|
|
156
|
+
export async function refreshRootWriteSelection(root, selection, fd) {
|
|
158
157
|
await authorizeRootWritePathSelection(selection);
|
|
159
|
-
|
|
160
|
-
inspectSelectionBindingSync(root, selection, fd);
|
|
158
|
+
inspectSelectionBindingSync(root, selection, fd);
|
|
161
159
|
await assertAsyncDirectoryGuard(selection.parentGuard);
|
|
162
160
|
}
|
|
163
|
-
export async function refreshRetainedRootWriteAdmission(root, selection,
|
|
161
|
+
export async function refreshRetainedRootWriteAdmission(root, selection, fd) {
|
|
164
162
|
await getFsSafeTestHooks()?.beforePinnedWriteParentAdmission?.(selection.operationTargetPath);
|
|
165
|
-
await refreshRootWriteSelection(root, selection,
|
|
163
|
+
await refreshRootWriteSelection(root, selection, fd);
|
|
166
164
|
}
|
|
167
165
|
export async function resolveGuardedWritePathInRoot(root, params) {
|
|
168
166
|
const resolvedPath = await resolvePathInRoot(root, params.relativePath, {
|
|
@@ -263,9 +261,9 @@ export async function resolvePinnedWriteTargetInRoot(root, relativePath, request
|
|
|
263
261
|
if (!overwrite) {
|
|
264
262
|
try {
|
|
265
263
|
const existing = fsSync.statSync(resolved);
|
|
266
|
-
if (!existing.isFile())
|
|
264
|
+
if (!existing.isFile() && !existing.isDirectory())
|
|
267
265
|
throw new FsSafeError("not-file", "not a file");
|
|
268
|
-
if (existing.nlink > 1)
|
|
266
|
+
if (existing.isFile() && existing.nlink > 1)
|
|
269
267
|
throw hardlinkedPathNotAllowedError();
|
|
270
268
|
throw new FsSafeError("already-exists", "file already exists");
|
|
271
269
|
}
|
|
@@ -8,5 +8,4 @@ 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
10
|
export declare function normalizeSafePathSegment(value: string): string;
|
|
11
|
-
export declare function sanitizeSafePathSegment(value: string): string | undefined;
|
|
12
11
|
export declare function assertSafePathPrefix(prefix: string, options?: SafePathSegmentOptions): string;
|
|
@@ -58,10 +58,6 @@ export function normalizeSafePathSegment(value) {
|
|
|
58
58
|
.replace(/[^A-Za-z0-9._-]+/g, "-");
|
|
59
59
|
return trimHyphenEdges(sanitized);
|
|
60
60
|
}
|
|
61
|
-
export function sanitizeSafePathSegment(value) {
|
|
62
|
-
const trimmed = normalizeSafePathSegment(value);
|
|
63
|
-
return isSafePathSegment(trimmed, { allowDotPrefix: true }) ? trimmed : undefined;
|
|
64
|
-
}
|
|
65
61
|
export function assertSafePathPrefix(prefix, options = {}) {
|
|
66
62
|
// Prefixes are often derived from safe filenames. Normalize harmless
|
|
67
63
|
// filename characters first, but still reject real path-control bytes.
|
|
@@ -7,6 +7,7 @@ import { readFileDescriptorBoundedSync } from "./bounded-read.js";
|
|
|
7
7
|
import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard, inspectDirectoryIdentity } from "./directory-guard.js";
|
|
8
8
|
import { pinNodeDirectoryForMode, assertOwnedDirectory } from "./directory-mode-node.js";
|
|
9
9
|
import { FsSafeError } from "./errors.js";
|
|
10
|
+
import { hasNodeErrorCode } from "./path.js";
|
|
10
11
|
import { openPinnedFileSync } from "./pinned-open.js";
|
|
11
12
|
import { runPinnedWriteHelper } from "./pinned-write.js";
|
|
12
13
|
import { ensureTrailingSep } from "./root-context.js";
|
|
@@ -14,7 +15,7 @@ import { verifyAtomicWriteResult } from "./root-write-verification.js";
|
|
|
14
15
|
import { prepareSecretRead, secretReadError, trimSecretFileContent, } from "./secret-read-policy.js";
|
|
15
16
|
import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
16
17
|
import { serializePathWrite } from "./write-queue.js";
|
|
17
|
-
import { assertNoWindowsPathAlias, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
18
|
+
import { assertNoWindowsPathAlias, isForeignWindowsShareOrDevicePath, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
18
19
|
export const PRIVATE_SECRET_DIR_MODE = 0o700;
|
|
19
20
|
export const PRIVATE_SECRET_FILE_MODE = 0o600;
|
|
20
21
|
export function readSecretFileSync(filePath, label, options = {}) {
|
|
@@ -62,7 +63,8 @@ function isRelativeEscape(relativePath) {
|
|
|
62
63
|
}
|
|
63
64
|
function assertPathWithinRoot(rootDir, targetPath) {
|
|
64
65
|
const relative = path.relative(rootDir, targetPath);
|
|
65
|
-
|
|
66
|
+
// path.relative folds Unicode case on Windows, so it alone could admit another host.
|
|
67
|
+
if (!relative || isRelativeEscape(relative) || isForeignWindowsShareOrDevicePath(targetPath, [rootDir])) {
|
|
66
68
|
throw new Error(`Private secret path must stay under ${rootDir}.`);
|
|
67
69
|
}
|
|
68
70
|
}
|
|
@@ -172,9 +174,8 @@ async function ensurePrivateDirectory(rootDir, targetDir, mode) {
|
|
|
172
174
|
break;
|
|
173
175
|
}
|
|
174
176
|
catch (error) {
|
|
175
|
-
if (!
|
|
177
|
+
if (!hasNodeErrorCode(error, "ENOENT"))
|
|
176
178
|
throw error;
|
|
177
|
-
}
|
|
178
179
|
await assertAsyncDirectoryGuard(parentGuard);
|
|
179
180
|
created = await createPrivateDirectory(current, mode);
|
|
180
181
|
// EEXIST grants no initialization authority; both outcomes need fresh type checks.
|
|
@@ -212,8 +213,11 @@ function snapshotSecretFileWriteParams(params) {
|
|
|
212
213
|
durable: params.durable,
|
|
213
214
|
};
|
|
214
215
|
}
|
|
215
|
-
async function secretFileWriteQueueKey(filePath) {
|
|
216
|
+
async function secretFileWriteQueueKey(rootDir, filePath) {
|
|
217
|
+
assertNoWindowsPathAlias(rootDir, "filesystem", "private secret root uses a Windows filesystem namespace alias");
|
|
216
218
|
assertNoWindowsPathAlias(filePath, "filesystem", "private secret path uses a Windows filesystem namespace alias");
|
|
219
|
+
// Canonicalizing an outside path would probe it first; a foreign share contacts its host.
|
|
220
|
+
assertPathWithinRoot(resolvePathPreservingWindowsRoot(rootDir), path.resolve(filePath));
|
|
217
221
|
try {
|
|
218
222
|
return await canonicalPathFromExistingAncestor(filePath);
|
|
219
223
|
}
|
|
@@ -271,9 +275,8 @@ async function materializeSecretFileAtomic(params, createOnly) {
|
|
|
271
275
|
}
|
|
272
276
|
}
|
|
273
277
|
catch (error) {
|
|
274
|
-
if (!
|
|
278
|
+
if (!hasNodeErrorCode(error, "ENOENT"))
|
|
275
279
|
throw error;
|
|
276
|
-
}
|
|
277
280
|
}
|
|
278
281
|
await assertAsyncDirectoryGuard(rootGuard);
|
|
279
282
|
await assertAsyncDirectoryGuard(parentGuard);
|
|
@@ -310,7 +313,7 @@ async function materializeSecretFileAtomic(params, createOnly) {
|
|
|
310
313
|
}
|
|
311
314
|
export async function writeSecretFileAtomic(params) {
|
|
312
315
|
const ownedParams = snapshotSecretFileWriteParams(params);
|
|
313
|
-
const canonicalPath = await secretFileWriteQueueKey(ownedParams.filePath);
|
|
316
|
+
const canonicalPath = await secretFileWriteQueueKey(ownedParams.rootDir, ownedParams.filePath);
|
|
314
317
|
await serializePathWrite(canonicalPath, async () => {
|
|
315
318
|
await materializeSecretFileAtomic(ownedParams, false);
|
|
316
319
|
});
|
|
@@ -318,7 +321,7 @@ export async function writeSecretFileAtomic(params) {
|
|
|
318
321
|
export async function createSecretFileAtomic(params) {
|
|
319
322
|
try {
|
|
320
323
|
const ownedParams = snapshotSecretFileWriteParams(params);
|
|
321
|
-
const canonicalPath = await secretFileWriteQueueKey(ownedParams.filePath);
|
|
324
|
+
const canonicalPath = await secretFileWriteQueueKey(ownedParams.rootDir, ownedParams.filePath);
|
|
322
325
|
await serializePathWrite(canonicalPath, async () => {
|
|
323
326
|
await materializeSecretFileAtomic(ownedParams, true);
|
|
324
327
|
});
|
|
@@ -8,8 +8,6 @@ type SecureDirStat = {
|
|
|
8
8
|
};
|
|
9
9
|
export type ResolveSecureTempRootOptions = {
|
|
10
10
|
accessSync?: (path: string, mode?: number) => void;
|
|
11
|
-
/** @deprecated Unused. Repairs require descriptor-bound chmod. */
|
|
12
|
-
chmodSync?: (path: string, mode: number) => void;
|
|
13
11
|
descriptor?: SecureTempRootDescriptorAdapter;
|
|
14
12
|
fallbackPrefix: string;
|
|
15
13
|
getuid?: () => number | undefined;
|
|
@@ -2,6 +2,7 @@ import { syncFileBestEffort } from "./file-sync.js";
|
|
|
2
2
|
import fsSync, {} from "node:fs";
|
|
3
3
|
import fs, {} from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { assertExclusiveCreateLeaf } from "./exclusive-create.js";
|
|
5
6
|
import { assertDirectoryIdentitySync, createAsyncDirectoryGuard, } from "./directory-guard.js";
|
|
6
7
|
import { syncDirectoryBestEffort } from "./directory-durability.js";
|
|
7
8
|
import { isWindowsReservedDeviceName } from "./device-path.js";
|
|
@@ -184,6 +185,7 @@ export async function writeCallbackSibling(params) {
|
|
|
184
185
|
}
|
|
185
186
|
}
|
|
186
187
|
else {
|
|
188
|
+
assertExclusiveCreateLeaf(tempPath);
|
|
187
189
|
result = await Reflect.apply(write, params, [tempPath]);
|
|
188
190
|
}
|
|
189
191
|
assertParent();
|
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard } from "./directory-guard.js";
|
|
5
5
|
import { fitFileNameToPortableComponent, sanitizeUntrustedFileName } from "./filename.js";
|
|
6
6
|
import { applyDirectoryMode } from "./replace-file-descriptor.js";
|
|
7
|
+
import { AtomicIo, runAsync } from "./atomic-io.js";
|
|
7
8
|
import { realpathSync } from "./realpath.js";
|
|
8
9
|
import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
|
|
9
10
|
import { root } from "./root.js";
|
|
@@ -12,7 +13,7 @@ import { resolveSecureTempRoot } from "./secure-temp-dir.js";
|
|
|
12
13
|
import { resolveCallbackTempPath, writeCallbackSibling } from "./sibling-staged-file.js";
|
|
13
14
|
import { tempFile } from "./temp-target.js";
|
|
14
15
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
15
|
-
import { assertNoWindowsPathAlias, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
16
|
+
import { assertNoWindowsPathAlias, isForeignWindowsShareOrDevicePath, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
16
17
|
function buildTempName(tempPrefix) {
|
|
17
18
|
const safePrefix = assertSafePathPrefix(tempPrefix ?? ".fs-safe-stream", {
|
|
18
19
|
label: "sibling temp prefix",
|
|
@@ -35,12 +36,11 @@ export async function writeSiblingTempFile(options) {
|
|
|
35
36
|
const syncParentDir = options.syncParentDir === true;
|
|
36
37
|
await fs.mkdir(recursiveMkdirPath(dir), { recursive: true, mode: dirMode });
|
|
37
38
|
if (chmodDir) {
|
|
38
|
-
await applyDirectoryMode({
|
|
39
|
-
fsModule: fs,
|
|
39
|
+
await runAsync(applyDirectoryMode(AtomicIo.async(fs), {
|
|
40
40
|
dirPath: dir,
|
|
41
41
|
mode: dirMode,
|
|
42
42
|
ignoreChmodError: true,
|
|
43
|
-
});
|
|
43
|
+
}));
|
|
44
44
|
}
|
|
45
45
|
return await writeCallbackSibling({
|
|
46
46
|
tempDir: dir,
|
|
@@ -83,6 +83,9 @@ export async function writeViaSiblingTempPath(params) {
|
|
|
83
83
|
assertNoWindowsPathAlias(rootDir, "filesystem", "sibling temp root uses a Windows filesystem namespace alias");
|
|
84
84
|
const requestedTargetPath = path.resolve(targetPathInput);
|
|
85
85
|
assertNoWindowsPathAlias(requestedTargetPath, "filesystem", "sibling temp target uses a Windows filesystem namespace alias");
|
|
86
|
+
if (isForeignWindowsShareOrDevicePath(requestedTargetPath, [resolvePathPreservingWindowsRoot(rootDirInput), rootDir])) {
|
|
87
|
+
throw new Error("Target path is outside the allowed root");
|
|
88
|
+
}
|
|
86
89
|
let targetPath;
|
|
87
90
|
try {
|
|
88
91
|
const realDir = realpathSync.native(path.dirname(requestedTargetPath));
|
|
@@ -5,6 +5,7 @@ import { fileObservation } from "./file-observation.js";
|
|
|
5
5
|
import { readFileHandleBounded } from "./bounded-read.js";
|
|
6
6
|
import { openSidecarRoot } from "./sidecar-lock-root.js";
|
|
7
7
|
import { createNativeExclusiveFile } from "./native-operations.js";
|
|
8
|
+
import { assertExclusiveCreateLeaf } from "./exclusive-create.js";
|
|
8
9
|
import { sidecarLockRetryDelay, sidecarLockTimeout, isTransientLockFileDenial, maxTransientLockDenials, validateSidecarLockStaleMs, validateSidecarLockRetryOptions, validateSidecarLockCompromiseCheckIntervalMs, validateSidecarLockTimeoutMs, } from "./sidecar-lock-policy.js";
|
|
9
10
|
import { relativeSidecarLockPath, removeSidecarLockIfUnchanged, serializeSidecarLockPayload, sidecarReclaimGuardExists, } from "./sidecar-lock-reclaim.js";
|
|
10
11
|
import { awaitSidecarAdmissionBoundary, conditionalSidecarLockParser, observeHeldSidecarParser, observeSidecarLockParser, runSidecarAdmissionBoundary, } from "./sidecar-lock-admission-parser.js";
|
|
@@ -278,15 +279,21 @@ export async function acquireSidecarLock(options, context) {
|
|
|
278
279
|
handle = opened.handle;
|
|
279
280
|
}
|
|
280
281
|
else {
|
|
281
|
-
|
|
282
|
-
handle =
|
|
283
|
-
(await createNativeExclusiveFile(lockPath, 0o600)) ??
|
|
284
|
-
(await fs.open(lockPath, "wx", 0o600));
|
|
285
|
-
}
|
|
286
|
-
catch (createError) {
|
|
282
|
+
const failedOpen = (createError) => {
|
|
287
283
|
lockFileCreateDenied = isTransientLockFileDenial(createError, lockPath);
|
|
288
284
|
exclusiveCreateConflict = createError.code === "EEXIST";
|
|
289
285
|
throw createError;
|
|
286
|
+
};
|
|
287
|
+
handle = await createNativeExclusiveFile(lockPath, 0o600).catch(failedOpen) ?? null;
|
|
288
|
+
if (!handle) {
|
|
289
|
+
try {
|
|
290
|
+
assertExclusiveCreateLeaf(lockPath);
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
exclusiveCreateConflict = error.code === "EEXIST";
|
|
294
|
+
throw error;
|
|
295
|
+
}
|
|
296
|
+
handle = await fs.open(lockPath, "wx", 0o600).catch(failedOpen);
|
|
290
297
|
}
|
|
291
298
|
await handle.writeFile(raw, "utf8");
|
|
292
299
|
}
|
|
@@ -23,9 +23,6 @@ export declare function serializeSidecarLockPayload(payload: Record<string, unkn
|
|
|
23
23
|
};
|
|
24
24
|
export declare function relativeSidecarLockPath(lockRoot: Root, lockPath: string): string;
|
|
25
25
|
export declare function parseSidecarLockPayload(raw: string, parser?: (raw: string) => unknown): unknown;
|
|
26
|
-
export declare function readSidecarLockSnapshot(lockPath: string, options?: Parameters<typeof readSidecarLockRawSnapshot>[1] & {
|
|
27
|
-
parsePayload?: (raw: string) => unknown;
|
|
28
|
-
}): Promise<SidecarLockSnapshot | null>;
|
|
29
26
|
export declare function readSidecarLockRawSnapshot(lockPath: string, options?: {
|
|
30
27
|
lockRoot?: Root;
|
|
31
28
|
rejectNonFile?: boolean;
|
|
@@ -63,14 +63,6 @@ export function parseSidecarLockPayload(raw, parser) {
|
|
|
63
63
|
return null;
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
-
function missingSnapshotPath(error) {
|
|
67
|
-
if (error.code === "ENOENT")
|
|
68
|
-
return null;
|
|
69
|
-
throw error;
|
|
70
|
-
}
|
|
71
|
-
export async function readSidecarLockSnapshot(lockPath, options = {}) {
|
|
72
|
-
return parseSidecarLockSnapshot(await readSidecarLockRawSnapshot(lockPath, options), options.parsePayload);
|
|
73
|
-
}
|
|
74
66
|
async function readSidecarLockComparisonSnapshot(lockPath, options) {
|
|
75
67
|
const snapshot = await readSidecarLockRawSnapshot(lockPath, options);
|
|
76
68
|
const parsePayload = options?.parsePayload;
|
|
@@ -97,13 +89,7 @@ export async function readSidecarLockRawSnapshot(lockPath, options = {}) {
|
|
|
97
89
|
await opened.handle.close().catch(() => undefined);
|
|
98
90
|
}
|
|
99
91
|
}
|
|
100
|
-
|
|
101
|
-
try {
|
|
102
|
-
before = fsSync.lstatSync(lockPath, { bigint: true });
|
|
103
|
-
}
|
|
104
|
-
catch (error) {
|
|
105
|
-
before = missingSnapshotPath(error);
|
|
106
|
-
}
|
|
92
|
+
const before = lstatSidecarLockSync(lockPath);
|
|
107
93
|
if (!before)
|
|
108
94
|
return null;
|
|
109
95
|
if (!before.isFile() || before.isSymbolicLink()) {
|
|
@@ -142,13 +128,7 @@ export async function readSidecarLockRawSnapshot(lockPath, options = {}) {
|
|
|
142
128
|
if (!options.allowDescriptorIdentityDrift && !sameFileIdentity(before, opened))
|
|
143
129
|
return null;
|
|
144
130
|
const raw = (await readFileHandleBounded(handle, MAX_LOCK_PAYLOAD_BYTES)).toString("utf8");
|
|
145
|
-
|
|
146
|
-
try {
|
|
147
|
-
after = fsSync.lstatSync(lockPath, { bigint: true });
|
|
148
|
-
}
|
|
149
|
-
catch (error) {
|
|
150
|
-
after = missingSnapshotPath(error);
|
|
151
|
-
}
|
|
131
|
+
const after = lstatSidecarLockSync(lockPath);
|
|
152
132
|
if (!after || !after.isFile() || !sameFileIdentity(before, after))
|
|
153
133
|
return null;
|
|
154
134
|
return { raw, stat: after };
|
|
@@ -162,7 +142,9 @@ function lstatSidecarLockSync(lockPath) {
|
|
|
162
142
|
return fsSync.lstatSync(lockPath, { bigint: true });
|
|
163
143
|
}
|
|
164
144
|
catch (error) {
|
|
165
|
-
|
|
145
|
+
if (error.code === "ENOENT")
|
|
146
|
+
return null;
|
|
147
|
+
throw error;
|
|
166
148
|
}
|
|
167
149
|
}
|
|
168
150
|
export function readSidecarLockSnapshotSync(lockPath, parsePayload, options = {}) {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { BigIntStats } from "node:fs";
|
|
2
2
|
import { FsSafeError } from "./errors.js";
|
|
3
|
+
import type { StagedSymlinkFailureDetails } from "./staged-symlink-types.js";
|
|
3
4
|
import type { StagedFileCleanupReceipt, StagedFileFailureDetails, StagedFilePublication, StagedFileReceipt } from "./staged-file-types.js";
|
|
4
5
|
export declare function createStagedFileReceipt(directory: StagedFileReceipt["directory"], temporaryBasename: string, stat: BigIntStats): StagedFileReceipt;
|
|
5
|
-
export declare function
|
|
6
|
+
export declare function stagedFailure(kind: "file" | "symlink", error: unknown, details: StagedFileFailureDetails | StagedSymlinkFailureDetails): FsSafeError;
|
|
6
7
|
/** Finish owned cleanup and every close before surfacing publication evidence. */
|
|
7
8
|
export declare function settleStagedFile(params: {
|
|
8
9
|
temporaryBasename: string;
|
|
@@ -10,11 +10,16 @@ export function createStagedFileReceipt(directory, temporaryBasename, stat) {
|
|
|
10
10
|
}),
|
|
11
11
|
});
|
|
12
12
|
}
|
|
13
|
-
export function
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
export function stagedFailure(kind, error, details) {
|
|
14
|
+
let code = "helper-failed";
|
|
15
|
+
try {
|
|
16
|
+
code = error instanceof FsSafeError ? error.code
|
|
17
|
+
: error?.code === "EEXIST" ? "already-exists" : "helper-failed";
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// Uninspectable error metadata must not interrupt terminal settlement.
|
|
21
|
+
}
|
|
22
|
+
return new FsSafeError(code, `staged ${kind} ${details.phase} failed`, { cause: error, details });
|
|
18
23
|
}
|
|
19
24
|
/** Finish owned cleanup and every close before surfacing publication evidence. */
|
|
20
25
|
export async function settleStagedFile(params) {
|
|
@@ -49,7 +54,7 @@ export async function settleStagedFile(params) {
|
|
|
49
54
|
status,
|
|
50
55
|
resources,
|
|
51
56
|
});
|
|
52
|
-
throw
|
|
57
|
+
throw stagedFailure("file", error, {
|
|
53
58
|
phase: params.failure ? params.phase : "cleanup",
|
|
54
59
|
publication: params.publication,
|
|
55
60
|
cleanup,
|
|
@@ -53,14 +53,7 @@ export function registerTempPathForExit(tempPath, options) {
|
|
|
53
53
|
singleLinkFile: options?.singleLinkFile,
|
|
54
54
|
cleanupSync: options?.cleanupSync,
|
|
55
55
|
};
|
|
56
|
-
|
|
57
|
-
try {
|
|
58
|
-
entry.identity = fsSync.lstatSync(tempPath, { bigint: true });
|
|
59
|
-
}
|
|
60
|
-
catch {
|
|
61
|
-
// Callers that register before creation set the identity after opening.
|
|
62
|
-
}
|
|
63
|
-
}
|
|
56
|
+
// Only the caller's receipt or cleanup callback grants removal authority.
|
|
64
57
|
tempCleanupEntries.set(tempPath, entry);
|
|
65
58
|
const unregister = (() => {
|
|
66
59
|
if (tempCleanupEntries.get(tempPath) === entry)
|
|
@@ -20,4 +20,4 @@ export declare function validateInitialTempWorkspaceChild(stat: BigIntStats, own
|
|
|
20
20
|
export declare function childHasRequestedMode(stat: BigIntStats | Stats, mode: number): boolean;
|
|
21
21
|
export declare function admitTempWorkspaceChild(dir: string, expected: BigIntStats, parent: TempWorkspaceRootAdmission, mode: number): Promise<void> | undefined;
|
|
22
22
|
export declare function admitTempWorkspaceChildSync(dir: string, expected: BigIntStats, parent: TempWorkspaceRootAdmission, mode: number): void;
|
|
23
|
-
export declare function validateAdmittedTempWorkspaceChild(current: TempWorkspaceIdentityStat, ownerUid: number | undefined, mode: number):
|
|
23
|
+
export declare function validateAdmittedTempWorkspaceChild(current: TempWorkspaceIdentityStat, ownerUid: number | undefined, mode: number): void;
|
|
@@ -105,10 +105,7 @@ async function initializeTempWorkspaceChildMode(dir, expected, parent, mode) {
|
|
|
105
105
|
try {
|
|
106
106
|
await owner.apply(mode, { check: parent.assertCurrent });
|
|
107
107
|
const current = inspectDirectoryIdentitySync(dir, expected);
|
|
108
|
-
|
|
109
|
-
if (!childHasRequestedMode(current, mode)) {
|
|
110
|
-
throw new FsSafeError("path-mismatch", "temp workspace final mode could not be verified");
|
|
111
|
-
}
|
|
108
|
+
validateAdmittedTempWorkspaceChild(current, parent.ownerUid, mode);
|
|
112
109
|
parent.assertCurrent();
|
|
113
110
|
}
|
|
114
111
|
finally {
|
|
@@ -131,10 +128,7 @@ export function admitTempWorkspaceChildSync(dir, expected, parent, mode) {
|
|
|
131
128
|
try {
|
|
132
129
|
owner.apply(mode, parent.assertCurrent);
|
|
133
130
|
const current = inspectDirectoryIdentitySync(dir, expected);
|
|
134
|
-
|
|
135
|
-
if (!childHasRequestedMode(current, mode)) {
|
|
136
|
-
throw new FsSafeError("path-mismatch", "temp workspace final mode could not be verified");
|
|
137
|
-
}
|
|
131
|
+
validateAdmittedTempWorkspaceChild(current, parent.ownerUid, mode);
|
|
138
132
|
parent.assertCurrent();
|
|
139
133
|
}
|
|
140
134
|
finally {
|
|
@@ -146,5 +140,4 @@ export function validateAdmittedTempWorkspaceChild(current, ownerUid, mode) {
|
|
|
146
140
|
if (!childHasRequestedMode(current, mode)) {
|
|
147
141
|
throw new FsSafeError("path-mismatch", "temp workspace final mode could not be verified");
|
|
148
142
|
}
|
|
149
|
-
return current;
|
|
150
143
|
}
|
|
@@ -3,12 +3,7 @@ export type WriteTextAtomicOptions = Pick<ReplaceFileAtomicOptions, "beforeRenam
|
|
|
3
3
|
mode?: number;
|
|
4
4
|
dirMode?: number;
|
|
5
5
|
trailingNewline?: boolean;
|
|
6
|
-
/**
|
|
7
|
-
* When false, skip the temp-file and parent-directory fsync calls while
|
|
8
|
-
* preserving the temp-file replace/rename behavior.
|
|
9
|
-
*
|
|
10
|
-
* Defaults to true.
|
|
11
|
-
*/
|
|
6
|
+
/** Defaults to true; false skips file and parent fsync without changing replacement. */
|
|
12
7
|
durable?: boolean;
|
|
13
8
|
};
|
|
14
9
|
export declare function writeTextAtomic(filePath: string, content: string, options?: WriteTextAtomicOptions): Promise<void>;
|
|
@@ -7,7 +7,7 @@ import { guardedRenameSync, guardedRmSync } from "./guarded-mutation.js";
|
|
|
7
7
|
import { realpathSync } from "./realpath.js";
|
|
8
8
|
import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
|
|
9
9
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
10
|
-
import { hasWindowsPathAlias, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
10
|
+
import { hasWindowsPathAlias, isForeignWindowsShareOrDevicePath, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
11
11
|
const TRASH_DESTINATION_COLLISION_CODES = new Set(["EEXIST", "ENOTEMPTY", "ERR_FS_CP_EEXIST"]);
|
|
12
12
|
const TRASH_DESTINATION_RETRY_LIMIT = 4;
|
|
13
13
|
function getFsErrorCode(error) {
|
|
@@ -93,6 +93,10 @@ function assertAllowedTrashTarget(targetPath, allowedRoots) {
|
|
|
93
93
|
assertNoTrashPathAlias(targetPath, "target path");
|
|
94
94
|
const lexicalTarget = path.resolve(targetPath);
|
|
95
95
|
assertNoTrashPathAlias(lexicalTarget, "target path");
|
|
96
|
+
const resolvedRoots = resolveAllowedTrashRoots(allowedRoots);
|
|
97
|
+
if (isForeignWindowsShareOrDevicePath(lexicalTarget, [...allowedRoots, ...resolvedRoots])) {
|
|
98
|
+
throw new Error(`Refusing to trash path outside allowed roots: ${targetPath}`);
|
|
99
|
+
}
|
|
96
100
|
const stat = fs.lstatSync(lexicalTarget);
|
|
97
101
|
const resolvedTarget = resolveTrashTargetPath(targetPath);
|
|
98
102
|
const resolvedTargetPath = resolvedTarget.path;
|
|
@@ -100,7 +104,7 @@ function assertAllowedTrashTarget(targetPath, allowedRoots) {
|
|
|
100
104
|
// Admit the directory entry only when its parent really stays inside an
|
|
101
105
|
// allowed root. Do not admit it because the symlink target is inside.
|
|
102
106
|
const resolvedParent = resolveTrashEntryParent(lexicalTarget, targetPath);
|
|
103
|
-
const isAllowed =
|
|
107
|
+
const isAllowed = resolvedRoots.some((root) => isSameOrChildPath(resolvedParent, root));
|
|
104
108
|
if (!isAllowed) {
|
|
105
109
|
throw new Error(`Refusing to trash path outside allowed roots: ${targetPath}`);
|
|
106
110
|
}
|