@openclaw/openshell-sandbox 2026.10.5-beta.4 → 2026.10.10-beta.2
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/README.md +1 -1
- package/node_modules/@openclaw/fs-safe/CHANGELOG.md +148 -0
- package/node_modules/@openclaw/fs-safe/README.md +20 -4
- package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/advanced.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-merge.js +28 -3
- package/node_modules/@openclaw/fs-safe/dist/archive-parser.wasm +0 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-read.js +11 -5
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-loader.js +4 -2
- package/node_modules/@openclaw/fs-safe/dist/archive.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/atomic-io.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/bounded-read.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/bounded-read.js +9 -2
- package/node_modules/@openclaw/fs-safe/dist/clone-metadata.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/clone-metadata.js +2 -1
- package/node_modules/@openclaw/fs-safe/dist/config.d.ts +6 -0
- package/node_modules/@openclaw/fs-safe/dist/config.js +5 -0
- package/node_modules/@openclaw/fs-safe/dist/copy-file-input.d.ts +3 -1
- package/node_modules/@openclaw/fs-safe/dist/copy-file-input.js +8 -7
- package/node_modules/@openclaw/fs-safe/dist/copy-link.d.ts +26 -0
- package/node_modules/@openclaw/fs-safe/dist/copy-link.js +170 -0
- package/node_modules/@openclaw/fs-safe/dist/copy-metadata.d.ts +9 -0
- package/node_modules/@openclaw/fs-safe/dist/copy-metadata.js +39 -0
- package/node_modules/@openclaw/fs-safe/dist/copy-path-admission.js +11 -2
- package/node_modules/@openclaw/fs-safe/dist/copy-root-file-sync.d.ts +2 -4
- package/node_modules/@openclaw/fs-safe/dist/copy-root-file-sync.js +4 -2
- package/node_modules/@openclaw/fs-safe/dist/copy-tree-portable.js +3 -6
- package/node_modules/@openclaw/fs-safe/dist/copy.js +3 -3
- package/node_modules/@openclaw/fs-safe/dist/create.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/creation-boundary.js +7 -4
- package/node_modules/@openclaw/fs-safe/dist/darwin-acl.d.ts +12 -0
- package/node_modules/@openclaw/fs-safe/dist/darwin-acl.js +29 -0
- package/node_modules/@openclaw/fs-safe/dist/directory-durability.d.ts +7 -4
- package/node_modules/@openclaw/fs-safe/dist/directory-guard.js +20 -5
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.js +27 -2
- package/node_modules/@openclaw/fs-safe/dist/directory-receipt.d.ts +7 -7
- package/node_modules/@openclaw/fs-safe/dist/directory-receipt.js +9 -4
- package/node_modules/@openclaw/fs-safe/dist/durability.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/entry-publication.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/exact-identity.d.ts +17 -0
- package/node_modules/@openclaw/fs-safe/dist/exact-identity.js +29 -0
- package/node_modules/@openclaw/fs-safe/dist/file-handle-transfer.js +4 -2
- package/node_modules/@openclaw/fs-safe/dist/file-hash.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/file-hash.js +11 -9
- package/node_modules/@openclaw/fs-safe/dist/file-identity.d.ts +7 -1
- package/node_modules/@openclaw/fs-safe/dist/file-identity.js +27 -5
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-admission.js +1 -2
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync.js +8 -1
- package/node_modules/@openclaw/fs-safe/dist/file-lock.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/file-lock.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/file-write-lease.d.ts +9 -0
- package/node_modules/@openclaw/fs-safe/dist/file-write-lease.js +42 -0
- package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.js +22 -15
- package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +3 -0
- package/node_modules/@openclaw/fs-safe/dist/hardlink-fallback.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/hardlink-fallback.js +5 -0
- package/node_modules/@openclaw/fs-safe/dist/index.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/index.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-ownership.js +16 -3
- package/node_modules/@openclaw/fs-safe/dist/local-file-descriptor.js +24 -13
- package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +85 -18
- package/node_modules/@openclaw/fs-safe/dist/native-config.d.ts +5 -0
- package/node_modules/@openclaw/fs-safe/dist/native-config.js +11 -0
- package/node_modules/@openclaw/fs-safe/dist/native-directory-observation.js +6 -4
- package/node_modules/@openclaw/fs-safe/dist/native-fallback-warning.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/native-fallback-warning.js +26 -2
- package/node_modules/@openclaw/fs-safe/dist/native-guidance.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/native-guidance.js +21 -0
- package/node_modules/@openclaw/fs-safe/dist/native-move-fallback.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/native-move-fallback.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/native-operations.js +8 -6
- package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.d.ts +44 -4
- package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.js +85 -15
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write-windows.d.ts +3 -3
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write-windows.js +33 -6
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.d.ts +4 -1
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +22 -3
- package/node_modules/@openclaw/fs-safe/dist/native-policy-directory-observation.js +4 -3
- package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.js +3 -0
- package/node_modules/@openclaw/fs-safe/dist/native-staged-file-metadata.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/native-staged-file-metadata.js +16 -0
- package/node_modules/@openclaw/fs-safe/dist/native-staged-file.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/native-staged-file.js +15 -5
- package/node_modules/@openclaw/fs-safe/dist/native-stat-identity.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/native-stat-identity.js +7 -0
- package/node_modules/@openclaw/fs-safe/dist/native-status.d.ts +17 -0
- package/node_modules/@openclaw/fs-safe/dist/native-status.js +30 -0
- package/node_modules/@openclaw/fs-safe/dist/native.d.ts +31 -4
- package/node_modules/@openclaw/fs-safe/dist/native.js +107 -9
- package/node_modules/@openclaw/fs-safe/dist/opened-realpath.js +9 -1
- package/node_modules/@openclaw/fs-safe/dist/overwrite-file-handle.js +4 -1
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/permission-exec.js +9 -1
- package/node_modules/@openclaw/fs-safe/dist/permissions-public.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/permissions-public.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/permissions-windows.js +83 -47
- package/node_modules/@openclaw/fs-safe/dist/pinned-open.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/pinned-open.js +8 -3
- package/node_modules/@openclaw/fs-safe/dist/pinned-write-input.js +13 -0
- package/node_modules/@openclaw/fs-safe/dist/pinned-write-staged.js +10 -8
- package/node_modules/@openclaw/fs-safe/dist/pinned-write-types.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/pinned-write.js +7 -5
- package/node_modules/@openclaw/fs-safe/dist/pipe.d.ts +9 -0
- package/node_modules/@openclaw/fs-safe/dist/pipe.js +26 -0
- package/node_modules/@openclaw/fs-safe/dist/private-directory.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/private-producer-handoff.js +2 -5
- package/node_modules/@openclaw/fs-safe/dist/private-temp-workspace.d.ts +3 -2
- package/node_modules/@openclaw/fs-safe/dist/private-temp-workspace.js +38 -10
- package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +92 -10
- package/node_modules/@openclaw/fs-safe/dist/publish-file-failure.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/publish-file-failure.js +3 -0
- package/node_modules/@openclaw/fs-safe/dist/publish-file.d.ts +5 -3
- package/node_modules/@openclaw/fs-safe/dist/publish-file.js +26 -20
- package/node_modules/@openclaw/fs-safe/dist/read-opened-file.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/read-opened-file.js +3 -4
- package/node_modules/@openclaw/fs-safe/dist/realpath.js +32 -0
- package/node_modules/@openclaw/fs-safe/dist/regular-file.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/regular-file.js +11 -15
- package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +3 -2
- package/node_modules/@openclaw/fs-safe/dist/root-copy.d.ts +18 -0
- package/node_modules/@openclaw/fs-safe/dist/root-copy.js +128 -0
- package/node_modules/@openclaw/fs-safe/dist/root-create-native.d.ts +8 -13
- package/node_modules/@openclaw/fs-safe/dist/root-create-native.js +252 -27
- package/node_modules/@openclaw/fs-safe/dist/root-directory-creation.js +5 -1
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.d.ts +11 -8
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +35 -13
- package/node_modules/@openclaw/fs-safe/dist/root-file-open-batch-sync.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/root-file-open-batch-sync.js +108 -0
- package/node_modules/@openclaw/fs-safe/dist/root-file.d.ts +7 -0
- package/node_modules/@openclaw/fs-safe/dist/root-file.js +15 -0
- package/node_modules/@openclaw/fs-safe/dist/root-impl.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/root-impl.js +154 -175
- package/node_modules/@openclaw/fs-safe/dist/root-mkdir-native.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/root-mkdir-native.js +239 -0
- package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +37 -15
- package/node_modules/@openclaw/fs-safe/dist/root-native-creation-path.d.ts +32 -0
- package/node_modules/@openclaw/fs-safe/dist/root-native-creation-path.js +56 -0
- package/node_modules/@openclaw/fs-safe/dist/root-options.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/root-public-types.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/root-remove-native.js +95 -16
- package/node_modules/@openclaw/fs-safe/dist/root-remove.js +6 -4
- package/node_modules/@openclaw/fs-safe/dist/root-walk.js +23 -19
- package/node_modules/@openclaw/fs-safe/dist/root-write-admission.js +8 -6
- package/node_modules/@openclaw/fs-safe/dist/root-write-publication.js +5 -1
- package/node_modules/@openclaw/fs-safe/dist/secure-file-windows.js +3 -1
- package/node_modules/@openclaw/fs-safe/dist/secure-file.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/secure-file.js +10 -4
- package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-retained.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-target.js +8 -1
- package/node_modules/@openclaw/fs-safe/dist/staged-directory.d.ts +13 -0
- package/node_modules/@openclaw/fs-safe/dist/staged-directory.js +29 -0
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.js +5 -3
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-descriptor.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-descriptor.js +13 -2
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-owner.js +71 -3
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.js +22 -0
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-types.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/test-hooks.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/test-hooks.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/trash.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/types.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-native.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/windows-appcontainer.d.ts +5 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-appcontainer.js +39 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-owner.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-owner.js +25 -36
- package/node_modules/@openclaw/fs-safe/dist/windows-security-command.js +24 -5
- package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.js +4 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-security-helper.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-security-helper.exe +0 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-security-helper.js +7 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-test-hooks.d.ts +16 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-test-hooks.js +51 -0
- package/node_modules/@openclaw/fs-safe/dist/write-file-handle.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/write-file-handle.js +12 -10
- package/node_modules/@openclaw/fs-safe/docs/advanced.md +2 -1
- package/node_modules/@openclaw/fs-safe/docs/archive.md +16 -0
- package/node_modules/@openclaw/fs-safe/docs/config.md +71 -1
- package/node_modules/@openclaw/fs-safe/docs/contributing.md +45 -5
- package/node_modules/@openclaw/fs-safe/docs/copy.md +45 -5
- package/node_modules/@openclaw/fs-safe/docs/durability.md +34 -7
- package/node_modules/@openclaw/fs-safe/docs/errors.md +8 -0
- package/node_modules/@openclaw/fs-safe/docs/guest.md +2 -0
- package/node_modules/@openclaw/fs-safe/docs/install.md +47 -27
- package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.5.md +6 -6
- package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.6.md +1 -1
- package/node_modules/@openclaw/fs-safe/docs/native-helper.md +79 -6
- package/node_modules/@openclaw/fs-safe/docs/native.md +279 -8
- package/node_modules/@openclaw/fs-safe/docs/permissions.md +64 -15
- package/node_modules/@openclaw/fs-safe/docs/pipe.md +81 -0
- package/node_modules/@openclaw/fs-safe/docs/platform-capabilities.md +63 -0
- package/node_modules/@openclaw/fs-safe/docs/public-api.md +10 -0
- package/node_modules/@openclaw/fs-safe/docs/queue-link-free-design.md +237 -0
- package/node_modules/@openclaw/fs-safe/docs/quickstart.md +23 -3
- package/node_modules/@openclaw/fs-safe/docs/reading.md +56 -2
- package/node_modules/@openclaw/fs-safe/docs/regular-file.md +2 -0
- package/node_modules/@openclaw/fs-safe/docs/secret-file.md +5 -5
- package/node_modules/@openclaw/fs-safe/docs/secure-file.md +1 -1
- package/node_modules/@openclaw/fs-safe/docs/security-model.md +35 -0
- package/node_modules/@openclaw/fs-safe/docs/sidecar-lock.md +31 -13
- package/node_modules/@openclaw/fs-safe/docs/staged-file.md +18 -0
- package/node_modules/@openclaw/fs-safe/docs/store.md +15 -0
- package/node_modules/@openclaw/fs-safe/docs/temp.md +39 -0
- package/node_modules/@openclaw/fs-safe/docs/testing.md +13 -0
- package/node_modules/@openclaw/fs-safe/docs/types.md +51 -81
- package/node_modules/@openclaw/fs-safe/docs/walk.md +4 -1
- package/node_modules/@openclaw/fs-safe/docs/watch.md +3 -2
- package/node_modules/@openclaw/fs-safe/docs/writing.md +121 -14
- package/node_modules/@openclaw/fs-safe/package.json +21 -12
- 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-freebsd-arm64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-freebsd-arm64/package.json +29 -0
- package/node_modules/@openclaw/fs-safe-freebsd-x64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-freebsd-x64/package.json +29 -0
- 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-arm64-msvc/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-win32-arm64-msvc/package.json +29 -0
- 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/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.cs +0 -336
- package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.ps1 +0 -90
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import fsSync, {} from "node:fs";
|
|
2
|
-
import fs from "node:fs/promises";
|
|
3
2
|
import path from "node:path";
|
|
4
|
-
import { inspectDirectoryIdentity, inspectDirectoryIdentitySync } from "./directory-guard.js";
|
|
3
|
+
import { assertSyncDirectoryGuard, inspectDirectoryIdentity, inspectDirectoryIdentitySync } from "./directory-guard.js";
|
|
5
4
|
import { nodeDirectorySearchOnlyFlags } from "./directory-mode-node.js";
|
|
6
5
|
import { inspectNativeDirectoryObservation } from "./native-directory-observation.js";
|
|
7
6
|
import { FsSafeError } from "./errors.js";
|
|
7
|
+
import { hasSafeFileIdentity } from "./file-identity.js";
|
|
8
|
+
import { assertNativeCapabilities } from "./native.js";
|
|
8
9
|
import { captureNativeFdClose } from "./native-binding.js";
|
|
9
10
|
import { realpathSync } from "./realpath.js";
|
|
10
|
-
import { describeStagedDirectory, exactIdentityMatches } from "./staged-directory.js";
|
|
11
|
+
import { describeNativeStagedDirectory, describePolicyStagedDirectory, describeStagedDirectory, exactIdentityMatches } from "./staged-directory.js";
|
|
11
12
|
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
12
13
|
import { createSuppressedError } from "./suppressed-error.js";
|
|
13
14
|
import { assertNoWindowsPathAlias, pathForWindowsFilesystem } from "./windows-path-alias.js";
|
|
14
15
|
export function sameNativeIdentity(left, right) {
|
|
15
|
-
return left
|
|
16
|
+
return hasSafeFileIdentity(left) && hasSafeFileIdentity(right) &&
|
|
17
|
+
BigInt(left.dev) === BigInt(right.dev) && BigInt(left.ino) === BigInt(right.ino);
|
|
16
18
|
}
|
|
17
19
|
function unavailable(message) {
|
|
18
20
|
return new FsSafeError("helper-unavailable", message);
|
|
@@ -22,13 +24,67 @@ function assertParentAdmissionAvailable(binding) {
|
|
|
22
24
|
throw unavailable("native parent directory admission is unavailable");
|
|
23
25
|
}
|
|
24
26
|
captureNativeFdClose(binding);
|
|
27
|
+
assertNativeCapabilities(binding, "openBeneath");
|
|
28
|
+
}
|
|
29
|
+
/** Short native mutations need a retained descriptor, not a Node FileHandle. */
|
|
30
|
+
export function openExactNativeRootSync(binding, params) {
|
|
31
|
+
assertParentAdmissionAvailable(binding);
|
|
32
|
+
assertNoWindowsPathAlias(params.rootPath, "filesystem", "native root uses a Windows filesystem namespace alias");
|
|
33
|
+
const flags = (nodeDirectorySearchOnlyFlags()?.flags ?? fsSync.constants.O_RDONLY) |
|
|
34
|
+
(fsSync.constants.O_DIRECTORY ?? 0);
|
|
35
|
+
const fd = fsSync.openSync(pathForWindowsFilesystem(params.rootPath), flags);
|
|
36
|
+
let held = true;
|
|
37
|
+
const close = () => {
|
|
38
|
+
if (!held)
|
|
39
|
+
return;
|
|
40
|
+
held = false;
|
|
41
|
+
fsSync.closeSync(fd);
|
|
42
|
+
};
|
|
43
|
+
try {
|
|
44
|
+
const stat = inspectFileIdentitySync(() => fsSync.fstatSync(fd, { bigint: true }), params.rootIdentity);
|
|
45
|
+
if (!stat.isDirectory())
|
|
46
|
+
throw new FsSafeError("path-mismatch", "native root is not a directory");
|
|
47
|
+
return { fd, stat, close, [Symbol.dispose]: close };
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
try {
|
|
51
|
+
close();
|
|
52
|
+
}
|
|
53
|
+
catch (closeError) {
|
|
54
|
+
throw createSuppressedError(closeError, error, "native root admission and close failed");
|
|
55
|
+
}
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export function openExactNativeRootAdmissionSync(binding, params) {
|
|
60
|
+
return {
|
|
61
|
+
directoryFlags: (nodeDirectorySearchOnlyFlags()?.flags ?? fsSync.constants.O_RDONLY) | (fsSync.constants.O_DIRECTORY ?? 0),
|
|
62
|
+
exactRoot: true,
|
|
63
|
+
reportCloseErrors: params.reportCloseErrors,
|
|
64
|
+
operation: params.operation,
|
|
65
|
+
root: openExactNativeRootSync(binding, params),
|
|
66
|
+
rootPath: params.rootPath,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export function borrowNativeRootParent(admission) {
|
|
70
|
+
const guard = { dir: admission.rootPath, realPath: admission.rootPath, stat: admission.root.stat };
|
|
71
|
+
assertSyncDirectoryGuard(guard);
|
|
72
|
+
return { fd: admission.root.fd, guard, close() { } };
|
|
25
73
|
}
|
|
26
74
|
export async function openNativeRootAdmission(binding, params) {
|
|
27
75
|
assertParentAdmissionAvailable(binding);
|
|
28
76
|
const directoryFlags = (params.searchOnly ? nodeDirectorySearchOnlyFlags()?.flags ?? fsSync.constants.O_RDONLY : fsSync.constants.O_RDONLY) |
|
|
29
77
|
(fsSync.constants.O_DIRECTORY ?? 0);
|
|
30
78
|
assertNoWindowsPathAlias(params.rootPath, "filesystem", "native root uses a Windows filesystem namespace alias");
|
|
31
|
-
|
|
79
|
+
let fd = fsSync.openSync(pathForWindowsFilesystem(params.rootPath), directoryFlags);
|
|
80
|
+
const close = async () => {
|
|
81
|
+
if (fd < 0)
|
|
82
|
+
return;
|
|
83
|
+
const owned = fd;
|
|
84
|
+
fd = -1;
|
|
85
|
+
fsSync.closeSync(owned);
|
|
86
|
+
};
|
|
87
|
+
const root = { get fd() { return fd; }, close, [Symbol.asyncDispose]: close };
|
|
32
88
|
try {
|
|
33
89
|
const exactRoot = typeof params.rootIdentity?.dev === "bigint" &&
|
|
34
90
|
typeof params.rootIdentity.ino === "bigint";
|
|
@@ -38,10 +94,7 @@ export async function openNativeRootAdmission(binding, params) {
|
|
|
38
94
|
rootMatches = true;
|
|
39
95
|
}
|
|
40
96
|
else if (process.platform === "win32") {
|
|
41
|
-
|
|
42
|
-
throw unavailable("native root directory identity checks are unavailable");
|
|
43
|
-
}
|
|
44
|
-
const identity = binding.fstatIdentity(root.fd);
|
|
97
|
+
const identity = fsSync.fstatSync(root.fd, { bigint: true });
|
|
45
98
|
rootMatches = !params.rootIdentity || sameNativeIdentity(params.rootIdentity, identity);
|
|
46
99
|
}
|
|
47
100
|
else {
|
|
@@ -87,7 +140,8 @@ export async function openNativeParentAdmission(binding, rootAdmission, relative
|
|
|
87
140
|
const parentInput = relativeParentPath
|
|
88
141
|
? path.join(rootAdmission.rootPath, ...relativeParentPath.split("/"))
|
|
89
142
|
: rootAdmission.rootPath;
|
|
90
|
-
if (observation === "native-directory"
|
|
143
|
+
if ((observation === "native-directory" || observation === "native-publication") &&
|
|
144
|
+
process.platform === "win32" && rootAdmission.exactRoot &&
|
|
91
145
|
typeof binding.observeDirectory === "function") {
|
|
92
146
|
const stat = inspectFileIdentitySync(() => fsSync.fstatSync(parentFd, { bigint: true }));
|
|
93
147
|
let observed;
|
|
@@ -110,17 +164,33 @@ export async function openNativeParentAdmission(binding, rootAdmission, relative
|
|
|
110
164
|
}
|
|
111
165
|
const parentPath = realpathSync.native(pathForWindowsFilesystem(parentInput));
|
|
112
166
|
assertNoWindowsPathAlias(parentPath, "filesystem", "native parent uses a Windows filesystem namespace alias");
|
|
167
|
+
if (observation === "native-publication" && process.platform !== "win32" && rootAdmission.exactRoot) {
|
|
168
|
+
const captured = describeNativeStagedDirectory(binding, parentFd, parentPath);
|
|
169
|
+
return {
|
|
170
|
+
fd: parentFd,
|
|
171
|
+
close: () => closeFd(parentFd),
|
|
172
|
+
guard: { dir: parentPath, realPath: parentPath, stat: captured.stat },
|
|
173
|
+
stagedDirectory: captured.directory,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
if (observation === "native-directory" && process.platform !== "win32" && rootAdmission.exactRoot) {
|
|
177
|
+
const captured = describePolicyStagedDirectory(parentFd, parentPath, binding);
|
|
178
|
+
captured.disposeObservation?.();
|
|
179
|
+
return {
|
|
180
|
+
fd: parentFd,
|
|
181
|
+
close: () => closeFd(parentFd),
|
|
182
|
+
guard: { dir: parentPath, realPath: parentPath, stat: captured.stat },
|
|
183
|
+
stagedDirectory: captured.directory,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
113
186
|
const stagedDirectory = process.platform === "win32"
|
|
114
187
|
? undefined
|
|
115
188
|
: describeStagedDirectory(parentFd, parentPath);
|
|
116
189
|
const parentPathStat = rootAdmission.exactRoot
|
|
117
190
|
? await inspectDirectoryIdentity(parentPath, inspectFileIdentitySync(() => fsSync.fstatSync(parentFd, { bigint: true })))
|
|
118
|
-
: fsSync.lstatSync(pathForWindowsFilesystem(parentPath));
|
|
191
|
+
: fsSync.lstatSync(pathForWindowsFilesystem(parentPath), { bigint: true });
|
|
119
192
|
if (process.platform === "win32" && !rootAdmission.exactRoot) {
|
|
120
|
-
|
|
121
|
-
throw unavailable("native parent directory identity checks are unavailable");
|
|
122
|
-
}
|
|
123
|
-
const parentIdentity = binding.fstatIdentity(parentFd);
|
|
193
|
+
const parentIdentity = fsSync.fstatSync(parentFd, { bigint: true });
|
|
124
194
|
if (parentPathStat.isSymbolicLink() || !sameNativeIdentity(parentPathStat, parentIdentity)) {
|
|
125
195
|
throw new FsSafeError("path-mismatch", `${rootAdmission.operation} parent changed during resolution`);
|
|
126
196
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { NativeRootAdmission } from "./native-parent-admission.js";
|
|
2
2
|
import { type AnyAsyncDirectoryGuard } from "./directory-guard.js";
|
|
3
3
|
import type { FileIdentityStat } from "./file-identity.js";
|
|
4
|
-
import type
|
|
4
|
+
import { type NativeBinding } from "./native.js";
|
|
5
5
|
import type { PinnedWriteParams } from "./pinned-write-types.js";
|
|
6
|
-
export declare function runPinnedWriteWindows(binding: NativeBinding, params: PinnedWriteParams, root:
|
|
6
|
+
export declare function runPinnedWriteWindows(binding: NativeBinding, params: PinnedWriteParams, root: NativeRootAdmission["root"], parentFd: number, parentGuard: AnyAsyncDirectoryGuard): Promise<FileIdentityStat>;
|
|
@@ -8,6 +8,8 @@ import { assertSyncDirectoryGuard } from "./directory-guard.js";
|
|
|
8
8
|
import { FsSafeError } from "./errors.js";
|
|
9
9
|
import { nativeOpenFlags, removeNativeCreatedFileIfStillPinned, } from "./native-operations.js";
|
|
10
10
|
import { writePinnedInput } from "./pinned-write-input.js";
|
|
11
|
+
import { assertNativeCopyCompleted, createNativeCopyFile } from "./copy-file-input.js";
|
|
12
|
+
import { assertNativeCapabilities } from "./native.js";
|
|
11
13
|
import { captureNativeFdClose } from "./native-binding.js";
|
|
12
14
|
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
13
15
|
import { assertFinalSymlinkRejected } from "./root-symlink-policy.js";
|
|
@@ -15,6 +17,7 @@ import { classifyNativeRenameFailure } from "./native-rename-outcome.js";
|
|
|
15
17
|
import { cleanupPinnedFilePath } from "./file-cleanup.js";
|
|
16
18
|
import { createStagedFileReceipt, settleStagedFile } from "./staged-file-settlement.js";
|
|
17
19
|
import { exactIdentityMatches } from "./staged-directory.js";
|
|
20
|
+
import { restoreCopyMetadata } from "./copy-metadata.js";
|
|
18
21
|
import { inspectNativeDirectoryObservation } from "./native-directory-observation.js";
|
|
19
22
|
function closeWriteFd(closeFd, fd) {
|
|
20
23
|
if (fd === undefined)
|
|
@@ -50,6 +53,7 @@ function assertNamedParentCurrent(binding, guard, identity) {
|
|
|
50
53
|
assertSyncDirectoryGuard({ ...guard, stat: identity });
|
|
51
54
|
}
|
|
52
55
|
export async function runPinnedWriteWindows(binding, params, root, parentFd, parentGuard) {
|
|
56
|
+
assertNativeCapabilities(binding, "openBeneath", "renameNoReplace", "renameReplace", "fstatIdentity");
|
|
53
57
|
const closeFd = captureNativeFdClose(binding);
|
|
54
58
|
const parentPath = parentGuard.realPath;
|
|
55
59
|
let tempFd;
|
|
@@ -78,6 +82,9 @@ export async function runPinnedWriteWindows(binding, params, root, parentFd, par
|
|
|
78
82
|
try {
|
|
79
83
|
tempName = `.fs-safe-${randomUUID()}.tmp`;
|
|
80
84
|
params.assertBeforeMutation?.();
|
|
85
|
+
const copied = params.input.kind === "file" && !params.private
|
|
86
|
+
? await createNativeCopyFile(binding, params.input, parentFd, tempName, params.maxBytes)
|
|
87
|
+
: undefined;
|
|
81
88
|
if (params.private) {
|
|
82
89
|
const parentIdentity = fsSync.fstatSync(parentFd, { bigint: true });
|
|
83
90
|
privateHandle = await createFileHandle(path.join(parentPath, tempName), {
|
|
@@ -91,16 +98,25 @@ export async function runPinnedWriteWindows(binding, params, root, parentFd, par
|
|
|
91
98
|
});
|
|
92
99
|
tempFd = privateHandle.fd;
|
|
93
100
|
}
|
|
101
|
+
else if (copied) {
|
|
102
|
+
tempFd = copied.fd;
|
|
103
|
+
}
|
|
94
104
|
else {
|
|
95
105
|
tempFd = binding.openBeneath(parentFd, tempName, nativeOpenFlags(fsSync.constants.O_WRONLY | fsSync.constants.O_CREAT | fsSync.constants.O_EXCL)).fd;
|
|
96
106
|
}
|
|
97
107
|
const verificationIdentity = inspectFileIdentitySync(() => fsSync.fstatSync(tempFd, { bigint: true }));
|
|
98
108
|
tempIdentity = verificationIdentity;
|
|
109
|
+
if (params.input.kind === "file")
|
|
110
|
+
assertNativeCopyCompleted(params.input, copied);
|
|
99
111
|
// Creation is requested at 0600 in the binding, but a restrictive umask
|
|
100
112
|
// can remove owner access. Keep the unpublished inode private and
|
|
101
113
|
// reopenable until the published name has been identity-fenced.
|
|
102
|
-
|
|
103
|
-
|
|
114
|
+
if ((verificationIdentity.mode & 128n) === 0n)
|
|
115
|
+
fsSync.fchmodSync(tempFd, 0o600);
|
|
116
|
+
if (!copied)
|
|
117
|
+
await writePinnedInput(tempFd, params.input, params.maxBytes, params.assertBeforeMutation);
|
|
118
|
+
if (params.input.kind === "file")
|
|
119
|
+
restoreCopyMetadata(tempFd, params.input.metadata);
|
|
104
120
|
if (params.sync !== false) {
|
|
105
121
|
if (params.strictFileSync)
|
|
106
122
|
fsSync.fsyncSync(tempFd);
|
|
@@ -132,6 +148,9 @@ export async function runPinnedWriteWindows(binding, params, root, parentFd, par
|
|
|
132
148
|
}
|
|
133
149
|
assertFinalSymlinkRejected(path.join(parentPath, params.basename), params.rejectFinalSymlink);
|
|
134
150
|
}
|
|
151
|
+
// The authority callback itself may have cancelled an otherwise completed copy.
|
|
152
|
+
if (params.input.kind === "file")
|
|
153
|
+
params.input.signal?.throwIfAborted();
|
|
135
154
|
try {
|
|
136
155
|
if (params.overwrite === false) {
|
|
137
156
|
binding.renameNoReplace(parentFd, tempName, parentFd, params.basename);
|
|
@@ -150,17 +169,25 @@ export async function runPinnedWriteWindows(binding, params, root, parentFd, par
|
|
|
150
169
|
if (receipt)
|
|
151
170
|
publication = Object.freeze({ status: "published", staged: receipt, basename: params.basename, overwrite: false });
|
|
152
171
|
params.onPublished?.(verificationIdentity);
|
|
153
|
-
|
|
172
|
+
const metadataOnly = params.overwrite === false && params.input.kind === "buffer" &&
|
|
173
|
+
params.input.stageBeforePublish !== false && !params.private &&
|
|
174
|
+
!params.assertBeforeMutation && binding.openPublicationMetadata;
|
|
175
|
+
targetFd = metadataOnly
|
|
176
|
+
? metadataOnly(parentFd, params.basename)
|
|
177
|
+
: binding.openBeneath(parentFd, params.basename, nativeOpenFlags(fsSync.constants.O_RDONLY)).fd;
|
|
154
178
|
const targetStat = inspectFileIdentitySync(() => fsSync.fstatSync(targetFd, { bigint: true }), verificationIdentity);
|
|
155
|
-
const targetIdentity = binding.fstatIdentity(targetFd);
|
|
156
179
|
if (!targetStat.isFile()) {
|
|
157
180
|
throw new FsSafeError("path-mismatch", "native write target changed after rename");
|
|
158
181
|
}
|
|
159
182
|
// Native exclusive creation starts at 0600. Apply the requested mode only
|
|
160
183
|
// after reopening and fencing the published name, both so mode 000 stays
|
|
161
184
|
// verifiable and so broader modes are never exposed before that fence.
|
|
185
|
+
// Windows chmod only changes the read-only attribute; use the fresh target
|
|
186
|
+
// observation to avoid rewriting an unchanged attribute.
|
|
187
|
+
const modeChange = (targetStat.mode & 128n) !== BigInt(params.mode & 0o200);
|
|
162
188
|
try {
|
|
163
|
-
|
|
189
|
+
if (modeChange)
|
|
190
|
+
fsSync.fchmodSync(targetFd, params.mode);
|
|
164
191
|
// Windows FlushFileBuffers needs write access; the verification reopen is read-only.
|
|
165
192
|
if (params.sync !== false) {
|
|
166
193
|
if (params.strictFileSync)
|
|
@@ -194,7 +221,7 @@ export async function runPinnedWriteWindows(binding, params, root, parentFd, par
|
|
|
194
221
|
else
|
|
195
222
|
await params.verifyPublished?.(targetFd, verificationIdentity, parentGuard);
|
|
196
223
|
completed = true;
|
|
197
|
-
return { dev:
|
|
224
|
+
return { dev: targetStat.dev, ino: targetStat.ino };
|
|
198
225
|
}
|
|
199
226
|
catch (error) {
|
|
200
227
|
failure = { error };
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { FileIdentityStat } from "./file-identity.js";
|
|
2
|
-
import type
|
|
2
|
+
import { type NativeBinding } from "./native.js";
|
|
3
3
|
import type { PinnedWriteParams } from "./pinned-write-types.js";
|
|
4
|
+
type PinnedWriteCapabilitiesInput = Pick<PinnedWriteParams, "input" | "private" | "verifyPosixMode">;
|
|
5
|
+
export declare function getPinnedWriteNativeBinding(params: PinnedWriteCapabilitiesInput): NativeBinding | undefined;
|
|
4
6
|
export declare function runPinnedWriteNative(binding: NativeBinding, params: PinnedWriteParams, fallback: (input: PinnedWriteParams["input"]) => Promise<FileIdentityStat>): Promise<FileIdentityStat>;
|
|
7
|
+
export {};
|
|
@@ -56,12 +56,27 @@ import { runPinnedWriteWindows } from "./native-pinned-write-windows.js";
|
|
|
56
56
|
import { capturePolicyAwareNativeParent } from "./native-policy-parent.js";
|
|
57
57
|
import { openNativeParentAdmission, openNativeRootAdmission } from "./native-parent-admission.js";
|
|
58
58
|
import { assertNativeStaging, writeNativeStage } from "./native-staged-file.js";
|
|
59
|
+
import { assertNativeCapabilities, getNativeBinding } from "./native.js";
|
|
59
60
|
import { captureNativeFdClose } from "./native-binding.js";
|
|
60
61
|
import { cachedNoReplaceUnavailable } from "./native-noreplace.js";
|
|
61
62
|
import { isFsSafeNativeRequired } from "./native-config.js";
|
|
63
|
+
import { copyLinkAtParent } from "./copy-link.js";
|
|
64
|
+
const writeCapabilities = ["openBeneath", "mkdirBeneath", "renameNoReplace", "renameReplace"];
|
|
65
|
+
const posixWriteCapabilities = [...writeCapabilities, "createStagedFile", "stagedFileMatches", "removeStagedFile"];
|
|
66
|
+
function pinnedWriteCapabilities(params) {
|
|
67
|
+
const windows = process.platform === "win32";
|
|
68
|
+
const capabilities = windows ? [...writeCapabilities, "fstatIdentity"] : posixWriteCapabilities;
|
|
69
|
+
const nativeCopy = params.input.kind === "file" && !params.private &&
|
|
70
|
+
(windows || !params.verifyPosixMode);
|
|
71
|
+
return nativeCopy ? [...capabilities, "copyFileExclusive"] : capabilities;
|
|
72
|
+
}
|
|
73
|
+
export function getPinnedWriteNativeBinding(params) {
|
|
74
|
+
return getNativeBinding(...pinnedWriteCapabilities(params));
|
|
75
|
+
}
|
|
62
76
|
export async function runPinnedWriteNative(binding, params, fallback) {
|
|
63
77
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
64
78
|
try {
|
|
79
|
+
assertNativeCapabilities(binding, ...pinnedWriteCapabilities(params));
|
|
65
80
|
const closeFd = captureNativeFdClose(binding);
|
|
66
81
|
const windows = process.platform === "win32";
|
|
67
82
|
if (!windows) {
|
|
@@ -105,17 +120,21 @@ export async function runPinnedWriteNative(binding, params, fallback) {
|
|
|
105
120
|
params.assertBeforeMutation?.();
|
|
106
121
|
binding.mkdirBeneath(root.fd, params.relativeParentPath, 0o777);
|
|
107
122
|
}
|
|
108
|
-
const admitted = await openNativeParentAdmission(binding, rootAdmission, params.relativeParentPath);
|
|
123
|
+
const admitted = await openNativeParentAdmission(binding, rootAdmission, params.relativeParentPath, "native-publication");
|
|
109
124
|
parentFd = admitted.fd;
|
|
110
125
|
parentPath = admitted.guard.realPath;
|
|
111
126
|
directory = admitted.stagedDirectory;
|
|
112
127
|
parentPathStat = admitted.guard.stat;
|
|
113
128
|
}
|
|
114
129
|
const verificationGuard = { dir: parentPath, realPath: parentPath, stat: parentPathStat };
|
|
130
|
+
if (params.input.kind === "link") {
|
|
131
|
+
return await copyLinkAtParent(params, params.input, verificationGuard, { binding, fd: parentFd });
|
|
132
|
+
}
|
|
115
133
|
if (params.overwrite === false) {
|
|
116
134
|
try {
|
|
117
|
-
fsSync.lstatSync(path.join(parentPath, params.basename))
|
|
118
|
-
|
|
135
|
+
if (fsSync.lstatSync(path.join(parentPath, params.basename), { throwIfNoEntry: false })) {
|
|
136
|
+
throw Object.assign(new Error("destination already exists"), { code: "EEXIST" });
|
|
137
|
+
}
|
|
119
138
|
}
|
|
120
139
|
catch (error) {
|
|
121
140
|
if (error.code !== "ENOENT") {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { FsSafeError } from "./errors.js";
|
|
3
3
|
import { getFsSafeNativeConfig } from "./native-config.js";
|
|
4
|
+
import { isNativeStatIdentity, statIdentityFromNative } from "./native-stat-identity.js";
|
|
4
5
|
export class NativePolicyDirectoryMismatch extends FsSafeError {
|
|
5
6
|
constructor(cause) {
|
|
6
7
|
super("path-mismatch", "staging directory pathname changed", { cause });
|
|
@@ -39,8 +40,8 @@ export function nativePolicyDirectoryObserver(binding, fd, pathname) {
|
|
|
39
40
|
}
|
|
40
41
|
if (getFsSafeNativeConfig().mode !== mode)
|
|
41
42
|
return undefined;
|
|
42
|
-
if (!observed ||
|
|
43
|
-
|
|
43
|
+
if (!observed || !isNativeStatIdentity(observed.dev) ||
|
|
44
|
+
!isNativeStatIdentity(observed.ino) ||
|
|
44
45
|
typeof observed.mode !== "bigint" || observed.mode < 0n || observed.mode > 0xffffffffn ||
|
|
45
46
|
typeof observed.nlink !== "bigint" || observed.nlink < 0n || observed.nlink > 0xffffffffffffffffn ||
|
|
46
47
|
typeof observed.realPath !== "string" || observed.realPath.includes("\0") ||
|
|
@@ -53,7 +54,7 @@ export function nativePolicyDirectoryObserver(binding, fd, pathname) {
|
|
|
53
54
|
return Object.freeze({
|
|
54
55
|
canonicalPath: observed.realPath,
|
|
55
56
|
identity: Object.freeze({
|
|
56
|
-
dev: observed.dev, ino: observed.ino, mode: observed.mode, nlink: observed.nlink,
|
|
57
|
+
dev: statIdentityFromNative(observed.dev), ino: statIdentityFromNative(observed.ino), mode: observed.mode, nlink: observed.nlink,
|
|
57
58
|
}),
|
|
58
59
|
});
|
|
59
60
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type BigIntStats } from "node:fs";
|
|
2
2
|
import { type AsyncDirectoryGuard } from "./directory-guard.js";
|
|
3
|
-
import type
|
|
3
|
+
import { type NativeBinding } from "./native.js";
|
|
4
4
|
import type { NativeRootAdmission } from "./native-parent-admission.js";
|
|
5
5
|
import type { PinnedWriteParams } from "./pinned-write-types.js";
|
|
6
6
|
import { describeStagedDirectory, type PolicyStagedDirectory } from "./staged-directory.js";
|
|
@@ -54,6 +54,7 @@ import fsSync, {} from "node:fs";
|
|
|
54
54
|
import path from "node:path";
|
|
55
55
|
import { inspectDirectoryIdentity, inspectDirectoryIdentitySync, } from "./directory-guard.js";
|
|
56
56
|
import { FsSafeError } from "./errors.js";
|
|
57
|
+
import { assertNativeCapabilities } from "./native.js";
|
|
57
58
|
import { captureNativeFdClose } from "./native-binding.js";
|
|
58
59
|
import { NativePolicyDirectoryMismatch } from "./native-policy-directory-observation.js";
|
|
59
60
|
import { assertWindowsPolicyParentCurrent, closeWindowsPolicyParentAfterFailure, openWindowsPolicyParent, windowsParentObservation, } from "./native-policy-parent-windows.js";
|
|
@@ -76,6 +77,7 @@ function mkdirPosixPolicyChild(binding, parentFd, basename) {
|
|
|
76
77
|
if (typeof created === "boolean")
|
|
77
78
|
return created;
|
|
78
79
|
}
|
|
80
|
+
assertNativeCapabilities(binding, "mkdirBeneath");
|
|
79
81
|
binding.mkdirBeneath(parentFd, basename, 0o777);
|
|
80
82
|
return false;
|
|
81
83
|
}
|
|
@@ -122,6 +124,7 @@ function tryDescribePolicyPosixParent(binding, fd, pathname) {
|
|
|
122
124
|
export async function capturePolicyAwareNativeParent(binding, params, rootAdmission, windows, directoryFlags, fuseChildCreation = false) {
|
|
123
125
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
124
126
|
try {
|
|
127
|
+
assertNativeCapabilities(binding, "openBeneath");
|
|
125
128
|
const rootFd = rootAdmission.root.fd;
|
|
126
129
|
const closeFd = captureNativeFdClose(binding);
|
|
127
130
|
const observationDisposers = windows ? undefined : new Map();
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { NativeBinding } from "./native-binding.js";
|
|
2
|
+
/** Exact nonprivate stage facts from one fresh named/retained observation. */
|
|
3
|
+
export declare function nativeStagedFileMode(binding: NativeBinding, parentFd: number, name: string, fd: number, expectedMode?: number): number | undefined;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { FsSafeError } from "./errors.js";
|
|
2
|
+
/** Exact nonprivate stage facts from one fresh named/retained observation. */
|
|
3
|
+
export function nativeStagedFileMode(binding, parentFd, name, fd, expectedMode) {
|
|
4
|
+
if (typeof binding.stagedFileMetadata !== "function")
|
|
5
|
+
return undefined;
|
|
6
|
+
const stat = binding.stagedFileMetadata(parentFd, name, fd);
|
|
7
|
+
if (!stat || typeof stat.mode !== "bigint" || stat.mode < 0n || stat.mode > 0xffffffffn ||
|
|
8
|
+
typeof stat.nlink !== "bigint" || stat.nlink !== 1n || (stat.mode & 61440n) !== 32768n) {
|
|
9
|
+
throw new FsSafeError("path-mismatch", "staged entry no longer names the exclusive created file");
|
|
10
|
+
}
|
|
11
|
+
const mode = Number(stat.mode & 4095n);
|
|
12
|
+
if (expectedMode !== undefined && mode !== expectedMode) {
|
|
13
|
+
throw new FsSafeError("insecure-permissions", "filesystem did not enforce the staged file mode");
|
|
14
|
+
}
|
|
15
|
+
return mode;
|
|
16
|
+
}
|
|
@@ -5,7 +5,7 @@ import type { FileIdentityStat } from "./file-identity.js";
|
|
|
5
5
|
import { type NativeBinding } from "./native-binding.js";
|
|
6
6
|
import type { PinnedWriteInput, PinnedWriteParams } from "./pinned-write-types.js";
|
|
7
7
|
import type { PublishedFileReceipt, StagedFile, StagedFileCleanupReceipt, StagedFileReceipt } from "./staged-file-types.js";
|
|
8
|
-
export type NativeStagingBinding = NativeBinding & Required<Pick<NativeBinding, "createStagedFile" | "stagedFileMatches" | "removeStagedFile">>;
|
|
8
|
+
export type NativeStagingBinding = NativeBinding & Required<Pick<NativeBinding, "createStagedFile" | "stagedFileMatches" | "removeStagedFile" | "renameReplace" | "renameNoReplace">>;
|
|
9
9
|
export declare function assertNativeStaging(binding: NativeBinding): asserts binding is NativeStagingBinding;
|
|
10
10
|
type StagedPermissionPolicy = "private-creation" | "mode-only";
|
|
11
11
|
type NativeStageOwner = Readonly<{
|
|
@@ -60,13 +60,15 @@ import { FsSafeError } from "./errors.js";
|
|
|
60
60
|
import { assertSynchronousCallbackResult, isMutationAuthorityError } from "./mutation-authority.js";
|
|
61
61
|
import { captureNativeFdClose } from "./native-binding.js";
|
|
62
62
|
import { replayPinnedInput, writePinnedInput } from "./pinned-write-input.js";
|
|
63
|
+
import { nativeStagedFileMode } from "./native-staged-file-metadata.js";
|
|
63
64
|
import { assertNativeCopyCompleted, createNativeCopyFile } from "./copy-file-input.js";
|
|
64
|
-
import {
|
|
65
|
+
import { assertNativeStagedDirectoryCurrent, openStagedDirectory } from "./staged-directory.js";
|
|
65
66
|
import { assertFinalSymlinkRejected } from "./root-symlink-policy.js";
|
|
66
67
|
import { classifyNativeRenameFailure } from "./native-rename-outcome.js";
|
|
67
68
|
import { createStagedFileReceipt, stagedFailure } from "./staged-file-settlement.js";
|
|
68
69
|
import { noReplaceUnavailable, rememberNoReplaceUnavailable } from "./native-noreplace.js";
|
|
69
70
|
import { isFsSafeNativeRequired } from "./native-config.js";
|
|
71
|
+
import { restoreCopyMetadata } from "./copy-metadata.js";
|
|
70
72
|
export function assertNativeStaging(binding) {
|
|
71
73
|
if ([
|
|
72
74
|
binding.closeOwnedFd,
|
|
@@ -221,6 +223,9 @@ class NativeStagedFile {
|
|
|
221
223
|
}
|
|
222
224
|
#assertNamed(name, expectedMode) {
|
|
223
225
|
const fd = this.#file();
|
|
226
|
+
const mode = !this.#private ? nativeStagedFileMode(this.#owner.binding, this.#owner.parentFd, name, fd, this.#verifyMode ? expectedMode : undefined) : undefined;
|
|
227
|
+
if (mode !== undefined)
|
|
228
|
+
return mode;
|
|
224
229
|
const stat = this.#owner.binding.stagedFileMatches(this.#owner.parentFd, name, fd)
|
|
225
230
|
? fs.fstatSync(fd, { bigint: true }) : undefined;
|
|
226
231
|
if (!stat || stat.nlink !== 1n) {
|
|
@@ -233,9 +238,12 @@ class NativeStagedFile {
|
|
|
233
238
|
if (this.#open().publication.status !== "not-published") {
|
|
234
239
|
throw new FsSafeError("helper-failed", "staged file publication has already been attempted");
|
|
235
240
|
}
|
|
236
|
-
|
|
241
|
+
this.#assertDirectory();
|
|
237
242
|
this.#assertNamed(this.#owner.name, 0o600);
|
|
238
243
|
}
|
|
244
|
+
#assertDirectory() {
|
|
245
|
+
assertNativeStagedDirectoryCurrent(this.#owner.binding, this.#owner.parentFd, this.#owner.directory);
|
|
246
|
+
}
|
|
239
247
|
#assertPermissions(fd, stat, expectedMode) {
|
|
240
248
|
if (this.#private)
|
|
241
249
|
assertPrivateCreationFile(stat, fd);
|
|
@@ -259,7 +267,7 @@ class NativeStagedFile {
|
|
|
259
267
|
throw new FsSafeError("helper-unavailable", "required cloning cannot verify staged permissions before copying");
|
|
260
268
|
}
|
|
261
269
|
}
|
|
262
|
-
|
|
270
|
+
this.#assertDirectory();
|
|
263
271
|
// The exclusive open performs no fallible post-open checks. Store its fd
|
|
264
272
|
// before every subsequent operation, including the first metadata read.
|
|
265
273
|
const state = this.#open();
|
|
@@ -299,6 +307,8 @@ class NativeStagedFile {
|
|
|
299
307
|
} : this.#assertBeforeMutation;
|
|
300
308
|
if (!copied)
|
|
301
309
|
await writePinnedInput(fd, input, maxBytes, assertBeforeMutation);
|
|
310
|
+
if (input.kind === "file")
|
|
311
|
+
restoreCopyMetadata(fd, input.metadata);
|
|
302
312
|
if (this.#verifyMode)
|
|
303
313
|
this.#assertStagePermissions(fd);
|
|
304
314
|
if (this.#owner.sync !== false) {
|
|
@@ -386,7 +396,7 @@ class NativeStagedFile {
|
|
|
386
396
|
this.#open().publication = receipt;
|
|
387
397
|
onPublished?.(receipt.staged.identity);
|
|
388
398
|
const stagedMode = this.#assertNamed(basename, 0o600);
|
|
389
|
-
|
|
399
|
+
this.#assertDirectory();
|
|
390
400
|
// Keep contents private until the name passes its identity fence. Mode
|
|
391
401
|
// changes use the owned fd, including for final mode 000.
|
|
392
402
|
const fd = this.#file();
|
|
@@ -403,7 +413,7 @@ class NativeStagedFile {
|
|
|
403
413
|
if (this.#owner.sync !== false)
|
|
404
414
|
syncFileBestEffortSync(this.#owner.parentFd);
|
|
405
415
|
this.#assertNamed(basename, this.#owner.mode);
|
|
406
|
-
|
|
416
|
+
this.#assertDirectory();
|
|
407
417
|
return receipt;
|
|
408
418
|
}
|
|
409
419
|
#finalize() {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Native stat identities are unsigned; Node BigIntStats preserves them as signed bits. */
|
|
2
|
+
export function statIdentityFromNative(value) {
|
|
3
|
+
return value <= 0x7fffffffffffffffn || process.versions.bun ? value : BigInt.asIntN(64, value);
|
|
4
|
+
}
|
|
5
|
+
export function isNativeStatIdentity(value) {
|
|
6
|
+
return typeof value === "bigint" && value >= 0n && value <= 0xffffffffffffffffn;
|
|
7
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type FsSafeNativeMode } from "./native-config.js";
|
|
2
|
+
export type FsSafeNativeStatusReason = "not-probed" | "loaded" | "disabled" | "unsupported-target" | "package-missing" | "load-failed" | "capability-missing";
|
|
3
|
+
export type FsSafeNativeStatus = {
|
|
4
|
+
mode: FsSafeNativeMode;
|
|
5
|
+
modeSource: "programmatic configuration" | "FS_SAFE_NATIVE_MODE" | "OPENCLAW_FS_SAFE_NATIVE_MODE" | "default";
|
|
6
|
+
reason: FsSafeNativeStatusReason;
|
|
7
|
+
loaded: boolean;
|
|
8
|
+
target: string;
|
|
9
|
+
packageName?: string;
|
|
10
|
+
packageVersion?: string;
|
|
11
|
+
loadErrorCode?: string;
|
|
12
|
+
guidance?: string;
|
|
13
|
+
};
|
|
14
|
+
/** Read this module instance's cached native outcome, optionally probing once. */
|
|
15
|
+
export declare function readFsSafeNativeStatus(options?: {
|
|
16
|
+
probe?: boolean;
|
|
17
|
+
}): FsSafeNativeStatus;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { getNativeModeSetting } from "./native-config.js";
|
|
2
|
+
import { nativeGuidance } from "./native-guidance.js";
|
|
3
|
+
import { getNativeBinding, getNativeFallbackDiagnostic } from "./native.js";
|
|
4
|
+
/** Read this module instance's cached native outcome, optionally probing once. */
|
|
5
|
+
export function readFsSafeNativeStatus(options) {
|
|
6
|
+
const { mode, source: modeSource } = getNativeModeSetting();
|
|
7
|
+
if (options?.probe && mode !== "off") {
|
|
8
|
+
try {
|
|
9
|
+
getNativeBinding();
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
// Required-mode load failures are cached; health checks report them.
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const diagnostic = getNativeFallbackDiagnostic();
|
|
16
|
+
const guidance = nativeGuidance(diagnostic.reason, modeSource);
|
|
17
|
+
return {
|
|
18
|
+
mode,
|
|
19
|
+
modeSource,
|
|
20
|
+
reason: diagnostic.reason,
|
|
21
|
+
loaded: diagnostic.reason === "loaded",
|
|
22
|
+
target: diagnostic.target,
|
|
23
|
+
...(diagnostic.packageName === undefined ? {} : {
|
|
24
|
+
packageName: diagnostic.packageName,
|
|
25
|
+
packageVersion: diagnostic.packageVersion,
|
|
26
|
+
}),
|
|
27
|
+
...(diagnostic.reason === "load-failed" ? { loadErrorCode: diagnostic.code } : {}),
|
|
28
|
+
...(guidance === undefined ? {} : { guidance }),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -1,5 +1,30 @@
|
|
|
1
|
-
import { type NativeBinding } from "./native-binding.js";
|
|
1
|
+
import { type NativeBinding, type CapableNativeBinding } from "./native-binding.js";
|
|
2
2
|
export type { NativeBinding } from "./native-binding.js";
|
|
3
|
+
/** Internal view of the cached load; never attempts to load an addon. */
|
|
4
|
+
export declare function getNativeFallbackDiagnostic(...capabilities: (keyof NativeBinding)[]): {
|
|
5
|
+
target: string;
|
|
6
|
+
packageName: string | undefined;
|
|
7
|
+
packageVersion: string | undefined;
|
|
8
|
+
reason: "disabled";
|
|
9
|
+
source: "FS_SAFE_NATIVE_MODE" | "OPENCLAW_FS_SAFE_NATIVE_MODE" | "default" | "programmatic configuration";
|
|
10
|
+
} | {
|
|
11
|
+
target: string;
|
|
12
|
+
packageName: string | undefined;
|
|
13
|
+
packageVersion: string | undefined;
|
|
14
|
+
reason: "capability-missing";
|
|
15
|
+
capabilities: (keyof NativeBinding)[];
|
|
16
|
+
} | {
|
|
17
|
+
reason: "not-probed" | "loaded" | "unsupported-target" | "package-missing";
|
|
18
|
+
target: string;
|
|
19
|
+
packageName: string | undefined;
|
|
20
|
+
packageVersion: string | undefined;
|
|
21
|
+
} | {
|
|
22
|
+
reason: "load-failed";
|
|
23
|
+
code: string;
|
|
24
|
+
target: string;
|
|
25
|
+
packageName: string | undefined;
|
|
26
|
+
packageVersion: string | undefined;
|
|
27
|
+
};
|
|
3
28
|
export declare function __loadBundledNativeForTest(): NativeBinding;
|
|
4
29
|
export declare function __nativeLoaderDetectorsForTest(): {
|
|
5
30
|
report: boolean | undefined;
|
|
@@ -7,7 +32,9 @@ export declare function __nativeLoaderDetectorsForTest(): {
|
|
|
7
32
|
elfInterpreter: boolean | undefined;
|
|
8
33
|
};
|
|
9
34
|
export declare function __nativeTargetForTest(platform: NodeJS.Platform, arch: string, musl?: boolean): string | undefined;
|
|
10
|
-
export declare function
|
|
11
|
-
export declare function
|
|
12
|
-
export declare function
|
|
35
|
+
export declare function selectNativeBinding<K extends keyof NativeBinding>(native: NativeBinding | undefined, ...capabilities: K[]): CapableNativeBinding<K> | undefined;
|
|
36
|
+
export declare function assertNativeCapabilities<K extends keyof NativeBinding>(native: NativeBinding, ...capabilities: K[]): asserts native is CapableNativeBinding<K>;
|
|
37
|
+
export declare function getNativeBinding<K extends keyof NativeBinding = never>(...capabilities: K[]): CapableNativeBinding<K> | undefined;
|
|
38
|
+
export declare function requireNativeBinding<K extends keyof NativeBinding = never>(...capabilities: K[]): CapableNativeBinding<K>;
|
|
39
|
+
export declare function __setNativeLoaderForTest(loader: () => NativeBinding, resolver?: (name: string) => string): void;
|
|
13
40
|
export declare function __resetNativeLoaderForTest(): void;
|