@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
|
@@ -3,7 +3,7 @@ import { closeSync, openSync, readSync, readdirSync, } from "node:fs";
|
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { FsSafeError } from "./errors.js";
|
|
5
5
|
import { captureNativeFdClose } from "./native-binding.js";
|
|
6
|
-
import { getFsSafeNativeConfig } from "./native-config.js";
|
|
6
|
+
import { getFsSafeNativeConfig, getNativeModeSetting } from "./native-config.js";
|
|
7
7
|
const require = createRequire(import.meta.url);
|
|
8
8
|
// Native registration creates process-lifetime housekeeping. Use the import
|
|
9
9
|
// context rather than retaining the first operation's AsyncLocalStorage stores.
|
|
@@ -12,6 +12,62 @@ let binding;
|
|
|
12
12
|
let loadError;
|
|
13
13
|
let attempted = false;
|
|
14
14
|
let loadBinding = loadBundledBinding;
|
|
15
|
+
let resolvePackage = (name) => require.resolve(name);
|
|
16
|
+
let loadOutcome = { reason: "not-probed" };
|
|
17
|
+
let selectedTarget;
|
|
18
|
+
let targetDescription;
|
|
19
|
+
function loadFailure(error, packageName) {
|
|
20
|
+
try {
|
|
21
|
+
return classifyLoadFailure(error, packageName);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// Diagnostic inspection must not replace an arbitrary thrown load value.
|
|
25
|
+
return { reason: "load-failed", code: "UNKNOWN" };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function classifyLoadFailure(error, packageName) {
|
|
29
|
+
const code = typeof error === "object" && error !== null && "code" in error ? error.code : undefined;
|
|
30
|
+
// Resolution must fail for the selected package itself. An exception inside
|
|
31
|
+
// an installed package (even one naming this package) is not package omission.
|
|
32
|
+
if (code === "MODULE_NOT_FOUND" && error instanceof Error &&
|
|
33
|
+
error.message.split("\n", 1)[0] === `Cannot find module '${packageName}'`) {
|
|
34
|
+
try {
|
|
35
|
+
resolvePackage(packageName);
|
|
36
|
+
}
|
|
37
|
+
catch (resolutionError) {
|
|
38
|
+
if (resolutionError instanceof Error && "code" in resolutionError &&
|
|
39
|
+
resolutionError.code === "MODULE_NOT_FOUND" &&
|
|
40
|
+
resolutionError.message.split("\n", 1)[0] === `Cannot find module '${packageName}'`) {
|
|
41
|
+
return { reason: "package-missing" };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// An allowlist excludes paths, secrets and arbitrary text in exception codes.
|
|
46
|
+
const knownCodes = ["ERR_DLOPEN_FAILED", "MODULE_NOT_FOUND", "ERR_MODULE_NOT_FOUND",
|
|
47
|
+
"ERR_PACKAGE_PATH_NOT_EXPORTED", "ERR_INVALID_PACKAGE_CONFIG", "EACCES", "EPERM", "ENOENT"];
|
|
48
|
+
return { reason: "load-failed", code: typeof code === "string" && knownCodes.includes(code) ? code : "UNKNOWN" };
|
|
49
|
+
}
|
|
50
|
+
/** Internal view of the cached load; never attempts to load an addon. */
|
|
51
|
+
export function getNativeFallbackDiagnostic(...capabilities) {
|
|
52
|
+
const setting = getNativeModeSetting();
|
|
53
|
+
const target = targetDescription === undefined ? bundledTarget() : selectedTarget;
|
|
54
|
+
const packageName = target === undefined ? undefined : nativePackageForTarget(target);
|
|
55
|
+
const manifest = require("../package.json");
|
|
56
|
+
const expected = {
|
|
57
|
+
target: targetDescription,
|
|
58
|
+
packageName,
|
|
59
|
+
packageVersion: packageName === undefined ? undefined : manifest.optionalDependencies[packageName],
|
|
60
|
+
};
|
|
61
|
+
if (setting.mode === "off")
|
|
62
|
+
return { ...expected, reason: "disabled", source: setting.source };
|
|
63
|
+
const loaded = binding;
|
|
64
|
+
if (loaded) {
|
|
65
|
+
const missing = capabilities.filter(name => typeof loaded[name] !== "function");
|
|
66
|
+
if (missing.length)
|
|
67
|
+
return { ...expected, reason: "capability-missing", capabilities: missing };
|
|
68
|
+
}
|
|
69
|
+
return { ...expected, ...loadOutcome };
|
|
70
|
+
}
|
|
15
71
|
function isMuslFilename(file) {
|
|
16
72
|
return file.includes("libc.musl-") || file.includes("ld-musl-");
|
|
17
73
|
}
|
|
@@ -127,19 +183,24 @@ function isMusl() {
|
|
|
127
183
|
return false;
|
|
128
184
|
}
|
|
129
185
|
function targetFor(platform, arch, musl) {
|
|
130
|
-
if (platform === "win32" && arch === "x64")
|
|
131
|
-
return
|
|
186
|
+
if (platform === "win32" && (arch === "x64" || arch === "arm64"))
|
|
187
|
+
return `win32-${arch}-msvc`;
|
|
132
188
|
if (platform === "darwin" && arch === "x64")
|
|
133
189
|
return "darwin-x64";
|
|
134
190
|
if (platform === "darwin" && arch === "arm64")
|
|
135
191
|
return "darwin-arm64";
|
|
192
|
+
if (platform === "freebsd" && (arch === "x64" || arch === "arm64"))
|
|
193
|
+
return `freebsd-${arch}`;
|
|
136
194
|
if (platform === "linux" && (arch === "x64" || arch === "arm64")) {
|
|
137
195
|
return `linux-${arch}-${musl ? "musl" : "gnu"}`;
|
|
138
196
|
}
|
|
139
197
|
return undefined;
|
|
140
198
|
}
|
|
141
199
|
function bundledTarget() {
|
|
142
|
-
|
|
200
|
+
const musl = isMusl();
|
|
201
|
+
selectedTarget = targetFor(process.platform, process.arch, musl);
|
|
202
|
+
targetDescription = selectedTarget ?? `${process.platform}-${process.arch}${process.platform === "linux" ? `-${musl ? "musl" : "gnu"}` : ""}`;
|
|
203
|
+
return selectedTarget;
|
|
143
204
|
}
|
|
144
205
|
function nativePackageForTarget(target) {
|
|
145
206
|
return `@openclaw/fs-safe-${target}`;
|
|
@@ -147,9 +208,13 @@ function nativePackageForTarget(target) {
|
|
|
147
208
|
function loadBundledBinding() {
|
|
148
209
|
const target = bundledTarget();
|
|
149
210
|
if (!target) {
|
|
211
|
+
loadOutcome = { reason: "unsupported-target" };
|
|
150
212
|
throw new Error(`Unsupported OS or architecture: ${process.platform}-${process.arch}`);
|
|
151
213
|
}
|
|
152
214
|
const loaded = runNativeInitialization(() => require(nativePackageForTarget(target)));
|
|
215
|
+
if (typeof loaded.closeOwnedFd !== "function") {
|
|
216
|
+
loadOutcome = { reason: "capability-missing", capabilities: ["closeOwnedFd"] };
|
|
217
|
+
}
|
|
153
218
|
captureNativeFdClose(loaded);
|
|
154
219
|
return loaded;
|
|
155
220
|
}
|
|
@@ -166,7 +231,24 @@ export function __nativeLoaderDetectorsForTest() {
|
|
|
166
231
|
export function __nativeTargetForTest(platform, arch, musl = false) {
|
|
167
232
|
return targetFor(platform, arch, musl);
|
|
168
233
|
}
|
|
169
|
-
export function
|
|
234
|
+
export function selectNativeBinding(native, ...capabilities) {
|
|
235
|
+
if (!native)
|
|
236
|
+
return undefined;
|
|
237
|
+
const missing = capabilities.filter((name) => typeof native[name] !== "function");
|
|
238
|
+
if (missing.length === 0)
|
|
239
|
+
return native;
|
|
240
|
+
if (getFsSafeNativeConfig().mode === "require") {
|
|
241
|
+
throw new FsSafeError("helper-unavailable", `native fs-safe capabilities unavailable: ${missing.join(", ")}`);
|
|
242
|
+
}
|
|
243
|
+
return undefined;
|
|
244
|
+
}
|
|
245
|
+
export function assertNativeCapabilities(native, ...capabilities) {
|
|
246
|
+
const missing = capabilities.filter((name) => typeof native[name] !== "function");
|
|
247
|
+
if (missing.length) {
|
|
248
|
+
throw new FsSafeError("helper-unavailable", `native fs-safe capabilities unavailable: ${missing.join(", ")}`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
export function getNativeBinding(...capabilities) {
|
|
170
252
|
const { mode } = getFsSafeNativeConfig();
|
|
171
253
|
if (mode === "off")
|
|
172
254
|
return undefined;
|
|
@@ -174,15 +256,23 @@ export function getNativeBinding() {
|
|
|
174
256
|
attempted = true;
|
|
175
257
|
try {
|
|
176
258
|
const loaded = loadBinding();
|
|
259
|
+
if (typeof loaded.closeOwnedFd !== "function") {
|
|
260
|
+
loadOutcome = { reason: "capability-missing", capabilities: ["closeOwnedFd"] };
|
|
261
|
+
}
|
|
177
262
|
captureNativeFdClose(loaded);
|
|
178
263
|
binding = loaded;
|
|
264
|
+
loadOutcome = { reason: "loaded" };
|
|
179
265
|
}
|
|
180
266
|
catch (error) {
|
|
181
267
|
loadError = error;
|
|
268
|
+
if (loadOutcome.reason === "not-probed") {
|
|
269
|
+
selectedTarget ??= bundledTarget();
|
|
270
|
+
loadOutcome = loadFailure(error, nativePackageForTarget(selectedTarget ?? `${process.platform}-${process.arch}`));
|
|
271
|
+
}
|
|
182
272
|
}
|
|
183
273
|
}
|
|
184
274
|
if (binding)
|
|
185
|
-
return binding;
|
|
275
|
+
return selectNativeBinding(binding, ...capabilities);
|
|
186
276
|
if (mode === "require") {
|
|
187
277
|
throw new FsSafeError("helper-unavailable", "native fs-safe helper is unavailable", {
|
|
188
278
|
cause: loadError,
|
|
@@ -190,8 +280,8 @@ export function getNativeBinding() {
|
|
|
190
280
|
}
|
|
191
281
|
return undefined;
|
|
192
282
|
}
|
|
193
|
-
export function requireNativeBinding() {
|
|
194
|
-
const native = getNativeBinding();
|
|
283
|
+
export function requireNativeBinding(...capabilities) {
|
|
284
|
+
const native = getNativeBinding(...capabilities);
|
|
195
285
|
if (!native) {
|
|
196
286
|
throw new FsSafeError("helper-unavailable", "native fs-safe helper is unavailable", {
|
|
197
287
|
cause: loadError,
|
|
@@ -199,15 +289,23 @@ export function requireNativeBinding() {
|
|
|
199
289
|
}
|
|
200
290
|
return native;
|
|
201
291
|
}
|
|
202
|
-
export function __setNativeLoaderForTest(loader) {
|
|
292
|
+
export function __setNativeLoaderForTest(loader, resolver) {
|
|
203
293
|
binding = undefined;
|
|
204
294
|
loadError = undefined;
|
|
205
295
|
attempted = false;
|
|
206
296
|
loadBinding = loader;
|
|
297
|
+
loadOutcome = { reason: "not-probed" };
|
|
298
|
+
selectedTarget = undefined;
|
|
299
|
+
targetDescription = undefined;
|
|
300
|
+
resolvePackage = resolver ?? ((name) => require.resolve(name));
|
|
207
301
|
}
|
|
208
302
|
export function __resetNativeLoaderForTest() {
|
|
209
303
|
binding = undefined;
|
|
210
304
|
loadError = undefined;
|
|
211
305
|
attempted = false;
|
|
212
306
|
loadBinding = loadBundledBinding;
|
|
307
|
+
loadOutcome = { reason: "not-probed" };
|
|
308
|
+
selectedTarget = undefined;
|
|
309
|
+
targetDescription = undefined;
|
|
310
|
+
resolvePackage = (name) => require.resolve(name);
|
|
213
311
|
}
|
|
@@ -26,7 +26,15 @@ export async function resolveOpenedFileRealPathForFd(fd, handleStat, ioPath) {
|
|
|
26
26
|
: [];
|
|
27
27
|
for (const fdPath of fdCandidates) {
|
|
28
28
|
try {
|
|
29
|
-
|
|
29
|
+
// Linux procfs already reports the descriptor's physical pathname. Keep
|
|
30
|
+
// the fresh identity check without walking that canonical path again.
|
|
31
|
+
const fdLink = fdPath.startsWith("/proc/") ? fsSync.readlinkSync(fdPath) : undefined;
|
|
32
|
+
// The deletion suffix can also name a live entry or a symlink. Preserve
|
|
33
|
+
// canonicalization for that ambiguous spelling, including literal names.
|
|
34
|
+
const fdRealPath = fdLink !== undefined && !fdLink.endsWith(" (deleted)")
|
|
35
|
+
? fdLink : realpathSync.native(fdPath);
|
|
36
|
+
if (!path.isAbsolute(fdRealPath))
|
|
37
|
+
continue;
|
|
30
38
|
const fdRealStat = statOptions ? fsSync.statSync(fdRealPath, statOptions) : fsSync.statSync(fdRealPath);
|
|
31
39
|
if (sameFileIdentity(handleStat, fdRealStat)) {
|
|
32
40
|
return { realPath: fdRealPath, stat: fdRealStat };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
1
2
|
import { FsSafeError } from "./errors.js";
|
|
2
3
|
import { assertSynchronousCallbackResult } from "./mutation-authority.js";
|
|
3
4
|
import { readFileWindowFully } from "./positional-read.js";
|
|
@@ -10,7 +11,9 @@ export async function overwriteFileHandle(handle, data, options = {}) {
|
|
|
10
11
|
const beforeWrite = options.beforeWrite;
|
|
11
12
|
const payload = data.subarray(0, data.byteLength);
|
|
12
13
|
const payloadBytes = payload.byteLength;
|
|
13
|
-
const
|
|
14
|
+
const fd = handle.fd;
|
|
15
|
+
// Keep FileHandle's closed-handle error while avoiding a metadata I/O hop.
|
|
16
|
+
const stat = fd < 0 ? await handle.stat() : fs.fstatSync(fd);
|
|
14
17
|
if (!stat.isFile()) {
|
|
15
18
|
throw new FsSafeError("not-file", "overwrite handle must be a regular file");
|
|
16
19
|
}
|
|
@@ -57,7 +57,7 @@ export async function readOwnerAndDaclBatch(paths, options = {}) {
|
|
|
57
57
|
if (config.mode === "require") {
|
|
58
58
|
throw new FsSafeError("helper-unavailable", "Windows owner and DACL facts require an up-to-date native helper");
|
|
59
59
|
}
|
|
60
|
-
warnNativeFallback("windows-owner-dacl", "Windows owner and DACL inspection
|
|
60
|
+
warnNativeFallback("windows-owner-dacl", "Windows owner and DACL inspection launches the packaged .NET helper for each check.", "readOwnerAndDacl");
|
|
61
61
|
}
|
|
62
62
|
const facts = await readWindowsSecurityFactsBatch(selected, { timeoutMs, native: useNative, mode: config.mode });
|
|
63
63
|
return facts.map(projectOwnerAndDacl);
|
|
@@ -15,7 +15,7 @@ export function readOwnerAndDacl(targetPath) {
|
|
|
15
15
|
if (getFsSafeNativeConfig().mode === "require") {
|
|
16
16
|
throw new FsSafeError("helper-unavailable", "Windows owner and DACL facts require an up-to-date native helper");
|
|
17
17
|
}
|
|
18
|
-
warnNativeFallback("windows-owner-dacl", "Windows owner and DACL inspection
|
|
18
|
+
warnNativeFallback("windows-owner-dacl", "Windows owner and DACL inspection launches the packaged .NET helper for each check.", "readOwnerAndDacl");
|
|
19
19
|
}
|
|
20
20
|
const facts = typeof inspect === "function" ? inspect.call(native, targetPath) : readWindowsSecurityFactsCommand(targetPath);
|
|
21
21
|
return projectOwnerAndDacl(facts);
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { assertWindowsHelperAllowed } from "./windows-appcontainer.js";
|
|
1
2
|
import { execFile } from "node:child_process";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { promisify, types as utilTypes } from "node:util";
|
|
4
5
|
import { formatErrorDetail } from "./error-detail.js";
|
|
6
|
+
import { windowsSecurityHelperPath, explainWindowsSecurityHelperFailure } from "./windows-security-helper.js";
|
|
5
7
|
const execFileAsync = promisify(execFile);
|
|
6
8
|
export const DEFAULT_PERMISSION_EXEC_TIMEOUT_MS = 30_000;
|
|
7
9
|
export function formatPermissionErrorDetail(value) {
|
|
@@ -179,6 +181,7 @@ export function getPermissionCommandFailure(error, command, durationMs) {
|
|
|
179
181
|
return { command, durationMs: Math.round(durationMs), ...detail };
|
|
180
182
|
}
|
|
181
183
|
export async function executePermissionCommand(command, args, timeoutMs = DEFAULT_PERMISSION_EXEC_TIMEOUT_MS) {
|
|
184
|
+
assertWindowsHelperAllowed();
|
|
182
185
|
const startedAt = performance.now();
|
|
183
186
|
try {
|
|
184
187
|
return (await execFileAsync(command, args, {
|
|
@@ -190,6 +193,11 @@ export async function executePermissionCommand(command, args, timeoutMs = DEFAUL
|
|
|
190
193
|
}));
|
|
191
194
|
}
|
|
192
195
|
catch (err) {
|
|
193
|
-
|
|
196
|
+
const error = new PermissionCommandError(command, performance.now() - startedAt, err, timeoutMs);
|
|
197
|
+
if (command === windowsSecurityHelperPath() &&
|
|
198
|
+
["ENOENT", "EACCES", "ENOEXEC"].includes(dataProperty(err, "code")?.value)) {
|
|
199
|
+
explainWindowsSecurityHelperFailure(error);
|
|
200
|
+
}
|
|
201
|
+
throw error;
|
|
194
202
|
}
|
|
195
203
|
}
|
|
@@ -3,3 +3,4 @@ export type { PermissionCommandFailure } from "./permission-exec.js";
|
|
|
3
3
|
export { createPrivateDirectory, type CreatePrivateDirectoryOptions, } from "./private-directory.js";
|
|
4
4
|
export { readOwnerAndDacl, type OwnerAndDaclResult, type WindowsAccessControlEntry, type WindowsAceFlags, } from "./owner-dacl.js";
|
|
5
5
|
export { readOwnerAndDaclBatch } from "./owner-dacl-batch.js";
|
|
6
|
+
export { inspectDarwinAcl, type DarwinAclInspection } from "./darwin-acl.js";
|
|
@@ -2,3 +2,4 @@ export { formatOctal, formatPermissionDetail, formatPermissionRemediation, inspe
|
|
|
2
2
|
export { createPrivateDirectory, } from "./private-directory.js";
|
|
3
3
|
export { readOwnerAndDacl, } from "./owner-dacl.js";
|
|
4
4
|
export { readOwnerAndDaclBatch } from "./owner-dacl-batch.js";
|
|
5
|
+
export { inspectDarwinAcl } from "./darwin-acl.js";
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
|
+
import { FsSafeError } from "./errors.js";
|
|
2
3
|
import { getNativeBinding } from "./native.js";
|
|
3
4
|
import { executePermissionCommand } from "./permission-exec.js";
|
|
4
5
|
import { safeStat } from "./permissions.js";
|
|
5
6
|
import { normalizeLowercaseStringOrEmpty } from "./string-coerce.js";
|
|
6
7
|
import { resolveWindowsSystemCommand } from "./windows-command.js";
|
|
7
8
|
import { inspectWindowsOwner } from "./windows-owner.js";
|
|
9
|
+
import { assertWindowsAclRemediationAllowed } from "./windows-appcontainer.js";
|
|
8
10
|
import { hasWindowsPathAlias } from "./windows-path-alias.js";
|
|
9
11
|
const INHERIT_FLAGS = new Set(["I", "OI", "CI", "IO", "NP"]);
|
|
10
12
|
const WORLD_PRINCIPALS = new Set(["everyone", "users", "builtin\\users", "authenticated users", "nt authority\\authenticated users", "anonymous logon", "nt authority\\anonymous logon", "guests", "builtin\\guests", "interactive", "nt authority\\interactive", "network", "nt authority\\network", "local"]);
|
|
@@ -40,30 +42,29 @@ const normalize = (value) => normalizeLowercaseStringOrEmpty(value);
|
|
|
40
42
|
const defaultWindowsUserInfo = () => os.userInfo();
|
|
41
43
|
const defaultPermissionExec = executePermissionCommand;
|
|
42
44
|
function inspectWindowsPermissionsNative(targetPath, unverified) {
|
|
43
|
-
const native = getNativeBinding();
|
|
45
|
+
const native = getNativeBinding("readOwnerAndDacl");
|
|
44
46
|
if (!native)
|
|
45
47
|
return undefined;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (facts.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
...unverified,
|
|
52
|
-
source: "windows-acl",
|
|
53
|
-
worldWritable: facts.worldWritable,
|
|
54
|
-
groupWritable: facts.groupWritable,
|
|
55
|
-
worldReadable: facts.worldReadable,
|
|
56
|
-
groupReadable: facts.groupReadable,
|
|
57
|
-
ownerSid: facts.ownerSid,
|
|
58
|
-
ownerTrusted: facts.ownerClass !== "foreign",
|
|
59
|
-
aclSummary: `native owner=${facts.ownerClass} world=` +
|
|
60
|
-
`${facts.worldReadable ? "r" : "-"}${facts.worldWritable ? "w" : "-"} ` +
|
|
61
|
-
`group=${facts.groupReadable ? "r" : "-"}${facts.groupWritable ? "w" : "-"}`,
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
catch {
|
|
48
|
+
const facts = native.readOwnerAndDacl(targetPath);
|
|
49
|
+
if (facts.fallbackRequired) {
|
|
50
|
+
if (facts.currentAppContainerSid) {
|
|
51
|
+
throw new FsSafeError("helper-unavailable", "AppContainer ACL facts require an unavailable external helper");
|
|
52
|
+
}
|
|
65
53
|
return undefined;
|
|
66
54
|
}
|
|
55
|
+
return {
|
|
56
|
+
...unverified,
|
|
57
|
+
source: "windows-acl",
|
|
58
|
+
worldWritable: facts.worldWritable,
|
|
59
|
+
groupWritable: facts.groupWritable,
|
|
60
|
+
worldReadable: facts.worldReadable,
|
|
61
|
+
groupReadable: facts.groupReadable,
|
|
62
|
+
ownerSid: facts.ownerSid,
|
|
63
|
+
ownerTrusted: facts.ownerClass !== "foreign",
|
|
64
|
+
aclSummary: `native owner=${facts.ownerClass} world=` +
|
|
65
|
+
`${facts.worldReadable ? "r" : "-"}${facts.worldWritable ? "w" : "-"} ` +
|
|
66
|
+
`group=${facts.groupReadable ? "r" : "-"}${facts.groupWritable ? "w" : "-"}`,
|
|
67
|
+
};
|
|
67
68
|
}
|
|
68
69
|
export async function inspectWindowsPermissions(targetPath, unverified, opts) {
|
|
69
70
|
if (hasWindowsPathAlias(targetPath, "filesystem", "win32")) {
|
|
@@ -250,7 +251,38 @@ function summarizeWindowsOwnerAcl(owner) {
|
|
|
250
251
|
canWrite: (ace.mask & 0x500d_0156) !== 0,
|
|
251
252
|
};
|
|
252
253
|
});
|
|
253
|
-
|
|
254
|
+
const summary = summarizeWindowsAcl(entries, { USERSID: owner.currentUserSid });
|
|
255
|
+
if (owner.currentAppContainerSid) {
|
|
256
|
+
const own = summary.untrustedGroup.filter(entry => entry.sid === owner.currentAppContainerSid);
|
|
257
|
+
summary.trusted.push(...own);
|
|
258
|
+
summary.untrustedGroup = summary.untrustedGroup.filter(entry => entry.sid !== owner.currentAppContainerSid);
|
|
259
|
+
}
|
|
260
|
+
return { ok: true, entries, ...summary };
|
|
261
|
+
}
|
|
262
|
+
function hasUnmergedAppContainerAces(facts) {
|
|
263
|
+
// These unique full-control allow entries cannot be merged or have their masks
|
|
264
|
+
// rewritten by .NET's ACL normalization. SID trust stays with the summary owner.
|
|
265
|
+
const inheritance = facts.aces[0]?.flags.raw;
|
|
266
|
+
return !!facts.currentAppContainerSid && (inheritance === 0 || inheritance === 3) &&
|
|
267
|
+
new Set(facts.aces.map(ace => ace.sid)).size === facts.aces.length &&
|
|
268
|
+
facts.aces.every(ace => ace.aceType === "allow" && ace.mask === 0x001f01ff &&
|
|
269
|
+
ace.flags.raw === inheritance);
|
|
270
|
+
}
|
|
271
|
+
function compareWindowsSids(left, right) {
|
|
272
|
+
const a = left.split("-").slice(2).map(Number);
|
|
273
|
+
const b = right.split("-").slice(2).map(Number);
|
|
274
|
+
// Match .NET Framework SecurityIdentifier.CompareTo: authority, subauthority
|
|
275
|
+
// count, then signed 32-bit subtraction for each subauthority.
|
|
276
|
+
if (a[0] !== b[0])
|
|
277
|
+
return a[0] < b[0] ? -1 : 1;
|
|
278
|
+
if (a.length !== b.length)
|
|
279
|
+
return a.length - b.length;
|
|
280
|
+
for (let index = 1; index < a.length; index++) {
|
|
281
|
+
const difference = ((a[index] | 0) - (b[index] | 0)) | 0;
|
|
282
|
+
if (difference !== 0)
|
|
283
|
+
return difference;
|
|
284
|
+
}
|
|
285
|
+
return 0;
|
|
254
286
|
}
|
|
255
287
|
export async function inspectWindowsAcl(targetPath, opts) {
|
|
256
288
|
if (opts?.principalTranslationFailed) {
|
|
@@ -262,33 +294,35 @@ export async function inspectWindowsAcl(targetPath, opts) {
|
|
|
262
294
|
return summarizeWindowsOwnerAcl({ error: String(error), errorCause: error });
|
|
263
295
|
}
|
|
264
296
|
let owner;
|
|
265
|
-
if (process.platform === "win32" && opts?.
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
297
|
+
if (process.platform === "win32" && opts?.exec === undefined) {
|
|
298
|
+
const native = getNativeBinding("readOwnerAndDacl");
|
|
299
|
+
const stat = native && await safeStat(targetPath);
|
|
300
|
+
// Keep the named-query behavior for leaf links; the native reader follows them.
|
|
301
|
+
const facts = stat?.ok && !stat.isSymlink ? native?.readOwnerAndDacl(targetPath) : undefined;
|
|
302
|
+
const explicitAppContainer = facts && hasUnmergedAppContainerAces(facts);
|
|
303
|
+
if (facts && !facts.fallbackRequired && facts.isLocal && facts.aceListComplete && facts.unsupportedAceTypes.length === 0 &&
|
|
304
|
+
(facts.aces.every(ace => ace.flags.inherited && ace.mask !== 0) || explicitAppContainer)) {
|
|
305
|
+
// .NET normalizes explicit ACEs, but retains nonzero inherited ACEs in
|
|
306
|
+
// their original order without merging them. Other forms keep its query.
|
|
307
|
+
// Adapt descriptor facts only: the public summary owns its SID and rights
|
|
308
|
+
// classification, which differs from the native secure-read policy.
|
|
309
|
+
const aces = explicitAppContainer
|
|
310
|
+
? [...facts.aces].sort((a, b) => compareWindowsSids(a.sid, b.sid)) : facts.aces;
|
|
311
|
+
owner = {
|
|
312
|
+
daclPresent: facts.daclPresent,
|
|
313
|
+
currentUserSid: facts.currentUserSid,
|
|
314
|
+
currentAppContainerSid: facts.currentAppContainerSid ?? undefined,
|
|
315
|
+
sid: facts.ownerSid,
|
|
316
|
+
aces: aces.map(ace => ({
|
|
317
|
+
sid: ace.sid,
|
|
318
|
+
mask: ace.mask,
|
|
319
|
+
deny: ace.aceType === "deny",
|
|
320
|
+
inheritOnly: ace.flags.inheritOnly,
|
|
321
|
+
})),
|
|
322
|
+
};
|
|
288
323
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
// when the optional helper or native descriptor query is unavailable.
|
|
324
|
+
else if (facts?.currentAppContainerSid) {
|
|
325
|
+
throw new FsSafeError("helper-unavailable", "AppContainer ACL normalization requires an unavailable external helper");
|
|
292
326
|
}
|
|
293
327
|
}
|
|
294
328
|
owner ??= await inspectWindowsOwner({ targetPath, env: opts?.env, exec: opts?.exec ?? defaultPermissionExec });
|
|
@@ -304,6 +338,8 @@ export function formatWindowsAclSummary(summary) {
|
|
|
304
338
|
return untrusted.length === 0 ? "trusted-only" : untrusted.map((entry) => `${entry.principal}:${entry.rawRights}`).join(", ");
|
|
305
339
|
}
|
|
306
340
|
export function formatIcaclsResetCommand(targetPath, opts) {
|
|
341
|
+
// Removing inherited ACEs can revoke the container's own access.
|
|
342
|
+
assertWindowsAclRemediationAllowed();
|
|
307
343
|
const command = resolveWindowsSystemCommand("icacls.exe", opts.env);
|
|
308
344
|
const user = resolveWindowsUserPrincipal(opts.env, opts.userInfo) ?? "%USERNAME%";
|
|
309
345
|
const grant = opts.isDir ? "(OI)(CI)F" : "F";
|
|
@@ -22,9 +22,11 @@ export declare function openPinnedFileSync(params: {
|
|
|
22
22
|
filePath: string;
|
|
23
23
|
resolvedPath?: string;
|
|
24
24
|
rejectPathSymlink?: boolean;
|
|
25
|
+
rejectLeafSymlinks?: boolean;
|
|
25
26
|
rejectHardlinks?: boolean;
|
|
26
27
|
maxBytes?: number;
|
|
27
28
|
allowedType?: PinnedOpenSyncAllowedType;
|
|
28
29
|
ioFs?: PinnedOpenSyncFs;
|
|
30
|
+
admittedFd?: number;
|
|
29
31
|
finalAdmission?: PinnedOpenSyncFinalAdmission;
|
|
30
32
|
}): PinnedOpenSyncResult;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { numberStats } from "./exact-identity.js";
|
|
1
2
|
import fs from "node:fs";
|
|
2
3
|
import { isUnsafeDeviceReadPath } from "./device-path.js";
|
|
3
4
|
import { FsSafeError } from "./errors.js";
|
|
@@ -17,10 +18,11 @@ export function openPinnedFileSync(params) {
|
|
|
17
18
|
const rejectPathSymlink = params.rejectPathSymlink === true;
|
|
18
19
|
const statPolicy = {
|
|
19
20
|
rejectHardlinks: params.rejectHardlinks,
|
|
21
|
+
rejectLeafSymlinks: params.rejectLeafSymlinks,
|
|
20
22
|
maxBytes: params.maxBytes,
|
|
21
23
|
};
|
|
22
24
|
const openReadFlags = resolveReadOpenFlags({ constants: ioFs.constants });
|
|
23
|
-
let fd = null;
|
|
25
|
+
let fd = params.admittedFd ?? null;
|
|
24
26
|
try {
|
|
25
27
|
assertNoWindowsPathAlias(filePath, "filesystem", "file path uses a Windows filesystem namespace alias");
|
|
26
28
|
if (resolvedPath !== undefined) {
|
|
@@ -46,8 +48,7 @@ export function openPinnedFileSync(params) {
|
|
|
46
48
|
assertAllowedStat(stat, allowedType, statPolicy);
|
|
47
49
|
return stat;
|
|
48
50
|
});
|
|
49
|
-
fd
|
|
50
|
-
const openedStat = ioFs.fstatSync(fd);
|
|
51
|
+
fd ??= ioFs.openSync(realPath, openReadFlags);
|
|
51
52
|
const identity = inspectFileIdentitySync(() => {
|
|
52
53
|
const stat = ioFs.fstatSync(fd, { bigint: true });
|
|
53
54
|
assertAllowedStat(stat, allowedType, statPolicy);
|
|
@@ -62,6 +63,7 @@ export function openPinnedFileSync(params) {
|
|
|
62
63
|
path: realPath,
|
|
63
64
|
descriptorIdentity: identity,
|
|
64
65
|
}) ?? realPath;
|
|
66
|
+
const openedStat = numberStats(identity);
|
|
65
67
|
const opened = { ok: true, path: admittedPath, fd, stat: openedStat, identity };
|
|
66
68
|
fd = null;
|
|
67
69
|
return opened;
|
|
@@ -86,6 +88,9 @@ export function openPinnedFileSync(params) {
|
|
|
86
88
|
}
|
|
87
89
|
}
|
|
88
90
|
function assertAllowedStat(stat, allowedType, params) {
|
|
91
|
+
if (stat.isSymbolicLink() && params.rejectLeafSymlinks) {
|
|
92
|
+
throw new FsSafeError("symlink", "symlink not allowed");
|
|
93
|
+
}
|
|
89
94
|
if (stat.isSymbolicLink() || !(allowedType === "directory" ? stat.isDirectory() : stat.isFile())) {
|
|
90
95
|
throw new FsSafeError("not-file", "path does not have the required file type");
|
|
91
96
|
}
|
|
@@ -21,6 +21,7 @@ export function replayPinnedInput(fd, input) {
|
|
|
21
21
|
})() };
|
|
22
22
|
}
|
|
23
23
|
const isUint8Array = types.isUint8Array;
|
|
24
|
+
const SMALL_BUFFER_BYTES = 64 * 1024;
|
|
24
25
|
function streamChunkBytes(chunk) {
|
|
25
26
|
if (Buffer.isBuffer(chunk))
|
|
26
27
|
return chunk;
|
|
@@ -29,10 +30,22 @@ function streamChunkBytes(chunk) {
|
|
|
29
30
|
return snapshotByteView(chunk);
|
|
30
31
|
}
|
|
31
32
|
export async function writePinnedInput(target, input, maxBytes, assertBeforeMutation) {
|
|
33
|
+
if (input.kind === "link")
|
|
34
|
+
throw new FsSafeError("helper-failed", "link copying requires the link publication path");
|
|
32
35
|
if (input.kind === "file") {
|
|
33
36
|
await writeCopyFileToFd(typeof target === "number" ? target : target.fd, input, maxBytes, assertBeforeMutation);
|
|
34
37
|
return;
|
|
35
38
|
}
|
|
39
|
+
if (typeof target === "number" && input.kind === "buffer" &&
|
|
40
|
+
input.stageBeforePublish !== false && !assertBeforeMutation) {
|
|
41
|
+
const data = typeof input.data === "string" ? Buffer.from(input.data, input.encoding ?? "utf8") : input.data;
|
|
42
|
+
if (data.byteLength <= SMALL_BUFFER_BYTES) {
|
|
43
|
+
if (maxBytes !== undefined && data.byteLength > maxBytes) {
|
|
44
|
+
throw new FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${data.byteLength})`);
|
|
45
|
+
}
|
|
46
|
+
return writeAllToFile(target, data, { synchronous: true });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
36
49
|
let bytes = 0;
|
|
37
50
|
const write = async (data) => {
|
|
38
51
|
bytes += data.byteLength;
|
|
@@ -3,6 +3,7 @@ import { assertExclusiveCreateLeaf } from "./exclusive-create.js";
|
|
|
3
3
|
import fsSync, {} from "node:fs";
|
|
4
4
|
import fs, {} from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
|
+
import { readFileHandleBounded } from "./bounded-read.js";
|
|
6
7
|
import { createFileHandle } from "./create.js";
|
|
7
8
|
import { hasPreservedCreationArtifacts, creationAdmissionFromParent } from "./creation-boundary.js";
|
|
8
9
|
import { syncDirectoryBestEffort } from "./directory-durability.js";
|
|
@@ -18,6 +19,7 @@ import { cleanupPinnedFilePath } from "./file-cleanup.js";
|
|
|
18
19
|
import { assertFinalSymlinkRejected } from "./root-symlink-policy.js";
|
|
19
20
|
import { createStagedFileReceipt, settleStagedFile } from "./staged-file-settlement.js";
|
|
20
21
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
22
|
+
import { restoreCopyMetadata } from "./copy-metadata.js";
|
|
21
23
|
export async function runPinnedStagedWrite(params, parentPath, parentGuard) {
|
|
22
24
|
const verifyPosixMode = process.platform !== "win32" &&
|
|
23
25
|
(params.verifyPosixMode === true || params.private === true);
|
|
@@ -31,7 +33,6 @@ export async function runPinnedStagedWrite(params, parentPath, parentGuard) {
|
|
|
31
33
|
? fsSync.constants.O_NOFOLLOW
|
|
32
34
|
: 0);
|
|
33
35
|
let handle;
|
|
34
|
-
let tempStat;
|
|
35
36
|
let tempIdentity;
|
|
36
37
|
let readHandle;
|
|
37
38
|
let renamed = false;
|
|
@@ -59,13 +60,15 @@ export async function runPinnedStagedWrite(params, parentPath, parentGuard) {
|
|
|
59
60
|
await writePinnedInput(handle, params.input, params.maxBytes, assertBeforeWrite);
|
|
60
61
|
if (verifyPosixMode)
|
|
61
62
|
assertPinnedWriteMode(handle.fd, 0o600, params.private);
|
|
62
|
-
tempStat = fsSync.fstatSync(handle.fd);
|
|
63
|
-
const tempPathStat = fsSync.lstatSync(tempPath);
|
|
63
|
+
const tempStat = fsSync.fstatSync(handle.fd, { bigint: true });
|
|
64
|
+
const tempPathStat = fsSync.lstatSync(tempPath, { bigint: true });
|
|
64
65
|
if (tempPathStat.isSymbolicLink() || !sameFileIdentity(tempPathStat, tempStat)) {
|
|
65
66
|
throw new FsSafeError("path-mismatch", "fallback temp path changed during write");
|
|
66
67
|
}
|
|
67
68
|
const expectedTempStat = tempStat;
|
|
68
|
-
|
|
69
|
+
fsSync.fchmodSync(handle.fd, params.mode);
|
|
70
|
+
if (params.input.kind === "file")
|
|
71
|
+
restoreCopyMetadata(handle.fd, params.input.metadata);
|
|
69
72
|
if (verifyPosixMode) {
|
|
70
73
|
assertPinnedWriteMode(handle.fd, params.mode, params.private);
|
|
71
74
|
assertBeforeMutation = pinnedWriteModeAssertion(handle.fd, params.mode, params.assertBeforeMutation, params.private);
|
|
@@ -115,7 +118,7 @@ export async function runPinnedStagedWrite(params, parentPath, parentGuard) {
|
|
|
115
118
|
await getFsSafeTestHooks()?.afterPinnedWriteFallbackRename?.(targetPath);
|
|
116
119
|
if (params.sync !== false)
|
|
117
120
|
await syncDirectoryBestEffort(parentPath);
|
|
118
|
-
const targetStat = fsSync.lstatSync(targetPath);
|
|
121
|
+
const targetStat = fsSync.lstatSync(targetPath, { bigint: true });
|
|
119
122
|
if (targetStat.isSymbolicLink()) {
|
|
120
123
|
throw new FsSafeError("path-mismatch", "fallback target changed during write");
|
|
121
124
|
}
|
|
@@ -133,14 +136,13 @@ export async function runPinnedStagedWrite(params, parentPath, parentGuard) {
|
|
|
133
136
|
const expectedHash = sha256Hex(params.input.data, params.input.encoding);
|
|
134
137
|
readHandle = await fs.open(targetPath, resolveReadOpenFlags());
|
|
135
138
|
const readHandleStat = fsSync.fstatSync(readHandle.fd, { bigint: true });
|
|
136
|
-
const actualHash = sha256Hex(await readHandle
|
|
139
|
+
const actualHash = sha256Hex(await readFileHandleBounded(readHandle, Infinity));
|
|
137
140
|
if (actualHash !== expectedHash) {
|
|
138
141
|
throw new FsSafeError("path-mismatch", "fallback target changed during write");
|
|
139
142
|
}
|
|
140
143
|
// The content-verified destination, not the old temp inode, is now pinned.
|
|
141
144
|
verificationIdentity = readHandleStat;
|
|
142
|
-
|
|
143
|
-
verifiedIdentity = { dev: Number(readHandleStat.dev), ino: Number(readHandleStat.ino) };
|
|
145
|
+
verifiedIdentity = readHandleStat;
|
|
144
146
|
}
|
|
145
147
|
});
|
|
146
148
|
await params.verifyPublished?.((readHandle ?? handle).fd, verificationIdentity, parentGuard);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CopyFileInput } from "./copy-file-input.js";
|
|
2
|
+
import type { CopyLinkInput } from "./copy-link.js";
|
|
2
3
|
import type { AnyAsyncDirectoryGuard } from "./directory-guard.js";
|
|
3
4
|
import type { FileIdentityStat } from "./file-identity.js";
|
|
4
5
|
import type { MutationDirectoryObservation } from "./pinned-mutation-observation.js";
|
|
@@ -16,7 +17,7 @@ export type PinnedWriteInput = {
|
|
|
16
17
|
kind: "stream";
|
|
17
18
|
stream: AsyncIterable<Uint8Array | string>;
|
|
18
19
|
stageBeforePublish?: boolean;
|
|
19
|
-
} | CopyFileInput;
|
|
20
|
+
} | CopyFileInput | CopyLinkInput;
|
|
20
21
|
export type RenameIdentityPolicy = "strict" | "verify-content-with-lock";
|
|
21
22
|
export type PublishedWriteIdentity = Readonly<{
|
|
22
23
|
dev: bigint;
|