@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,6 +1,9 @@
|
|
|
1
|
+
import type { ExactFileIdentity } from "./exact-identity.js";
|
|
1
2
|
export type FastPathMode = "auto" | "never" | "require";
|
|
2
3
|
export type SafeEncoding = BufferEncoding | null;
|
|
3
4
|
export type PathStat = {
|
|
5
|
+
/** Exact companion for identity checks; numeric fields are legacy metadata. */
|
|
6
|
+
exactIdentity?: ExactFileIdentity;
|
|
4
7
|
dev: number;
|
|
5
8
|
gid: number;
|
|
6
9
|
ino: number;
|
|
@@ -20,3 +23,4 @@ export type BasePathOptions = {
|
|
|
20
23
|
rootDir: string;
|
|
21
24
|
relativePath: string;
|
|
22
25
|
};
|
|
26
|
+
export type { ExactFileIdentity } from "./exact-identity.js";
|
|
@@ -5,9 +5,9 @@ import { watchStreamPaths } from "./watch-stream.js";
|
|
|
5
5
|
export function watchBinding(mode) {
|
|
6
6
|
if (mode === "poll")
|
|
7
7
|
return;
|
|
8
|
-
const binding = getNativeBinding(
|
|
8
|
+
const binding = getNativeBinding("watchRegister", "watchAdd", "watchUnregister", ...(process.platform === "darwin" ? ["watchConfigure", "watchEntries"] : []));
|
|
9
9
|
// Bun TSFN teardown remains unqualified; guarded native scans remain usable.
|
|
10
|
-
if (binding
|
|
10
|
+
if (binding && !process.versions.bun && !process.versions.deno && ["linux", "darwin", "win32"].includes(process.platform))
|
|
11
11
|
return binding;
|
|
12
12
|
if (mode === "events" || getFsSafeNativeConfig().mode === "require") {
|
|
13
13
|
throw new FsSafeError("helper-unavailable", "native watch events are unavailable", { details: { operation: "watch" } });
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type NativeBinding } from "./native.js";
|
|
2
|
+
/** The native owner caches immutable primary-token membership across Workers. */
|
|
3
|
+
export declare function isWindowsAppContainer(binding: NativeBinding): boolean | undefined;
|
|
4
|
+
export declare function assertWindowsHelperAllowed(): void;
|
|
5
|
+
export declare function assertWindowsAclRemediationAllowed(): void;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { FsSafeError } from "./errors.js";
|
|
2
|
+
import { getNativeBinding } from "./native.js";
|
|
3
|
+
/** The native owner caches immutable primary-token membership across Workers. */
|
|
4
|
+
export function isWindowsAppContainer(binding) {
|
|
5
|
+
if (!binding.isWindowsAppContainer)
|
|
6
|
+
return undefined;
|
|
7
|
+
try {
|
|
8
|
+
const membership = binding.isWindowsAppContainer();
|
|
9
|
+
if (typeof membership !== "boolean")
|
|
10
|
+
throw new Error("Invalid AppContainer membership");
|
|
11
|
+
return membership;
|
|
12
|
+
}
|
|
13
|
+
catch (cause) {
|
|
14
|
+
throw new FsSafeError("permission-unverified", "Windows process token membership could not be verified", { cause });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function assertWindowsHelperAllowed() {
|
|
18
|
+
if (process.platform !== "win32")
|
|
19
|
+
return;
|
|
20
|
+
const binding = getNativeBinding();
|
|
21
|
+
if (binding && isWindowsAppContainer(binding)) {
|
|
22
|
+
// Current libuv can hang while creating child-process pipes in a lowbox.
|
|
23
|
+
throw new FsSafeError("helper-unavailable", "External Windows security helpers are unavailable inside an AppContainer");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function assertWindowsAclRemediationAllowed() {
|
|
27
|
+
if (process.platform !== "win32")
|
|
28
|
+
return;
|
|
29
|
+
let binding;
|
|
30
|
+
try {
|
|
31
|
+
binding = getNativeBinding();
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return;
|
|
35
|
+
} // Command formatting remains available without an addon.
|
|
36
|
+
if (binding && isWindowsAppContainer(binding)) {
|
|
37
|
+
throw new FsSafeError("helper-unavailable", "Windows ACL reset commands are unavailable inside an AppContainer");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -6,6 +6,7 @@ export type WindowsOwnerExec = (command: string, args: string[]) => Promise<{
|
|
|
6
6
|
export type WindowsOwnerSummary = Omit<PermissionFailureFields & {
|
|
7
7
|
sid?: string;
|
|
8
8
|
currentUserSid?: string;
|
|
9
|
+
currentAppContainerSid?: string;
|
|
9
10
|
daclPresent?: boolean;
|
|
10
11
|
aces?: WindowsOwnerAce[];
|
|
11
12
|
aclError?: string;
|
|
@@ -1,38 +1,16 @@
|
|
|
1
|
-
import { formatCaughtPermissionFailure, getPermissionCommandFailure, } from "./permission-exec.js";
|
|
2
|
-
import {
|
|
1
|
+
import { formatCaughtPermissionFailure, getPermissionCommandFailure, executePermissionCommand, } from "./permission-exec.js";
|
|
2
|
+
import { assertWindowsHelperAllowed } from "./windows-appcontainer.js";
|
|
3
|
+
import { FsSafeError } from "./errors.js";
|
|
4
|
+
import { realpathSync } from "./realpath.js";
|
|
5
|
+
import { windowsSecurityHelperPath } from "./windows-security-helper.js";
|
|
3
6
|
import { hasWindowsPathAlias } from "./windows-path-alias.js";
|
|
7
|
+
import { APP_CONTAINER_SID_RE } from "./windows-security-facts.js";
|
|
4
8
|
const SID_RE = /^\*?s-\d+-\d+(-\d+)+$/i;
|
|
5
9
|
const TRUSTED_OWNER_SIDS = new Set(["s-1-5-18", "s-1-5-32-544"]);
|
|
6
10
|
function normalizeSid(value) {
|
|
7
11
|
const normalized = value.trim().toLowerCase();
|
|
8
12
|
return normalized.startsWith("*") ? normalized.slice(1) : normalized;
|
|
9
13
|
}
|
|
10
|
-
function encodePowerShellCommand(source) {
|
|
11
|
-
return Buffer.from(source, "utf16le").toString("base64");
|
|
12
|
-
}
|
|
13
|
-
function windowsOwnerQueryCommand(targetPath) {
|
|
14
|
-
const encodedPath = Buffer.from(targetPath, "utf8").toString("base64");
|
|
15
|
-
return [
|
|
16
|
-
"$ErrorActionPreference='Stop'",
|
|
17
|
-
"$env:PSModulePath=[IO.Path]::Combine($PSHOME,'Modules')",
|
|
18
|
-
`$p=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${encodedPath}'))`,
|
|
19
|
-
"$sections=[System.Security.AccessControl.AccessControlSections]::Access -bor [System.Security.AccessControl.AccessControlSections]::Owner",
|
|
20
|
-
"$acl=if([IO.Directory]::Exists($p)){[IO.Directory]::GetAccessControl($p,$sections)}else{[IO.File]::GetAccessControl($p,$sections)}",
|
|
21
|
-
"$ownerSid=$acl.GetOwner([System.Security.Principal.SecurityIdentifier]).Value",
|
|
22
|
-
"$currentSid=[System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value",
|
|
23
|
-
"$root=[IO.Path]::GetPathRoot($p)",
|
|
24
|
-
"$extendedDrive=$p.Length -ge 7 -and $p.StartsWith('\\\\?\\') -and [char]::IsLetter($p[4]) -and $p[5] -eq ':' -and $p[6] -eq '\\'",
|
|
25
|
-
"$driveRoot=if($extendedDrive){$p.Substring(4,3)}else{$root}",
|
|
26
|
-
"$namespacePath=$p.StartsWith('\\\\')",
|
|
27
|
-
"$remote=($namespacePath -and -not $extendedDrive) -or ([IO.DriveInfo]::new($driveRoot).DriveType -eq [IO.DriveType]::Network)",
|
|
28
|
-
// Emit only ASCII SID and numeric facts: console encodings can replace both
|
|
29
|
-
// Unicode pathnames and account names before JavaScript receives the bytes.
|
|
30
|
-
"$raw=[System.Security.AccessControl.RawSecurityDescriptor]::new($acl.GetSecurityDescriptorBinaryForm(),0)",
|
|
31
|
-
"$dacl=$raw.DiscretionaryAcl;$complete=$true",
|
|
32
|
-
"$aces=@(foreach($ace in $dacl){if($ace -isnot [System.Security.AccessControl.CommonAce] -or $ace.IsCallback -or [int]$ace.AceType -notin @(0,1)){$complete=$false;continue};@{sid=$ace.SecurityIdentifier.Value;mask=([long]$ace.AccessMask -band 4294967295);deny=([int]$ace.AceType -eq 1);inheritOnly=([int]$ace.AceFlags -band 8) -ne 0}})",
|
|
33
|
-
"@{ownerSid=$ownerSid;currentUserSid=$currentSid;daclPresent=($null -ne $dacl);aces=$aces;complete=$complete;remote=$remote}|Microsoft.PowerShell.Utility\\ConvertTo-Json -Depth 4 -Compress",
|
|
34
|
-
].join(";");
|
|
35
|
-
}
|
|
36
14
|
function parseWindowsAclFacts(parsed) {
|
|
37
15
|
if (parsed.complete !== true || typeof parsed.daclPresent !== "boolean" || !Array.isArray(parsed.aces)) {
|
|
38
16
|
return { aclError: "Windows ACL query returned incomplete descriptor data" };
|
|
@@ -53,18 +31,24 @@ export async function inspectWindowsOwner(params) {
|
|
|
53
31
|
const error = new Error("Path uses a Windows filesystem namespace alias");
|
|
54
32
|
return { error: String(error), errorCause: error };
|
|
55
33
|
}
|
|
34
|
+
if (process.platform === "win32" && params.exec === executePermissionCommand) {
|
|
35
|
+
assertWindowsHelperAllowed();
|
|
36
|
+
// Refuse a missing sandbox canonicalizer before starting a helper process.
|
|
37
|
+
// Other pathname failures retain the named-query reporting contract below.
|
|
38
|
+
try {
|
|
39
|
+
realpathSync.native(params.targetPath);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (error instanceof FsSafeError && error.code === "helper-unavailable")
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
56
46
|
let command = "";
|
|
57
47
|
let startedAt = performance.now();
|
|
58
48
|
try {
|
|
59
|
-
command =
|
|
49
|
+
command = windowsSecurityHelperPath();
|
|
60
50
|
startedAt = performance.now();
|
|
61
|
-
const { stdout } = await params.exec(command, [
|
|
62
|
-
"-NoLogo",
|
|
63
|
-
"-NoProfile",
|
|
64
|
-
"-NonInteractive",
|
|
65
|
-
"-EncodedCommand",
|
|
66
|
-
encodePowerShellCommand(windowsOwnerQueryCommand(params.targetPath)),
|
|
67
|
-
]);
|
|
51
|
+
const { stdout } = await params.exec(command, ["owner", params.targetPath]);
|
|
68
52
|
const value = JSON.parse(stdout.trim());
|
|
69
53
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
70
54
|
return { error: "Windows owner query returned invalid SID data" };
|
|
@@ -79,6 +63,10 @@ export async function inspectWindowsOwner(params) {
|
|
|
79
63
|
if (!ownerSid || !currentUserSid) {
|
|
80
64
|
return { error: "Windows owner query returned invalid SID data" };
|
|
81
65
|
}
|
|
66
|
+
if (parsed.currentAppContainerSid != null &&
|
|
67
|
+
(typeof parsed.currentAppContainerSid !== "string" || !APP_CONTAINER_SID_RE.test(parsed.currentAppContainerSid.toLowerCase()))) {
|
|
68
|
+
return { error: "Windows owner query returned invalid AppContainer SID data" };
|
|
69
|
+
}
|
|
82
70
|
const remote = parsed.remote;
|
|
83
71
|
if (typeof remote !== "boolean") {
|
|
84
72
|
return { error: "Windows owner query returned invalid locality data" };
|
|
@@ -86,6 +74,7 @@ export async function inspectWindowsOwner(params) {
|
|
|
86
74
|
return {
|
|
87
75
|
sid: ownerSid,
|
|
88
76
|
currentUserSid,
|
|
77
|
+
...(typeof parsed.currentAppContainerSid === "string" ? { currentAppContainerSid: normalizeSid(parsed.currentAppContainerSid) } : {}),
|
|
89
78
|
...parseWindowsAclFacts(parsed),
|
|
90
79
|
trusted: !remote && (ownerSid === currentUserSid || TRUSTED_OWNER_SIDS.has(ownerSid)),
|
|
91
80
|
};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { assertWindowsHelperAllowed } from "./windows-appcontainer.js";
|
|
1
2
|
import { spawn, spawnSync } from "node:child_process";
|
|
2
3
|
import { fileURLToPath } from "node:url";
|
|
3
4
|
import { FsSafeError } from "./errors.js";
|
|
4
5
|
import { DEFAULT_PERMISSION_EXEC_TIMEOUT_MS, PermissionCommandError } from "./permission-exec.js";
|
|
5
|
-
import { resolveWindowsSystemCommand } from "./windows-command.js";
|
|
6
6
|
import { parseWindowsOwnerAndDaclFacts, parseWindowsSecurityCommandFacts, unverified } from "./windows-security-facts.js";
|
|
7
|
+
import { windowsSecurityHelperPath, explainWindowsSecurityHelperFailure } from "./windows-security-helper.js";
|
|
7
8
|
const MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
8
9
|
const TERMINATION_GRACE_MS = 1_000;
|
|
9
10
|
const FULL_IDENTITY = /^[0-9a-f]{16}:[0-9a-f]{32}$/;
|
|
@@ -35,10 +36,10 @@ function command(operation, params) {
|
|
|
35
36
|
(!Number.isInteger(params.expectedLinks) || params.expectedLinks < 1 || params.expectedLinks > 0xffff_ffff)) {
|
|
36
37
|
throw new TypeError("Windows security operation requires a positive uint32 link count");
|
|
37
38
|
}
|
|
38
|
-
const
|
|
39
|
+
const helper = windowsSecurityHelperPath();
|
|
39
40
|
return {
|
|
40
|
-
file:
|
|
41
|
-
args: [
|
|
41
|
+
file: helper,
|
|
42
|
+
args: [operation],
|
|
42
43
|
// Preserve Windows UTF-16 spelling and leave long paths out of the bounded
|
|
43
44
|
// command line. This overrides any inherited value only in this child.
|
|
44
45
|
env: {
|
|
@@ -85,14 +86,28 @@ function parseReplyValue(stdout, operation) {
|
|
|
85
86
|
if (typeof response.code !== "string" || !codes.has(response.code) || typeof response.message !== "string") {
|
|
86
87
|
unverified("Windows security command returned an invalid failure");
|
|
87
88
|
}
|
|
88
|
-
|
|
89
|
+
if (response.errno !== undefined &&
|
|
90
|
+
(typeof response.errno !== "number" || !Number.isInteger(response.errno) || response.errno < 0 || response.errno > 0xffff_ffff)) {
|
|
91
|
+
unverified("Windows security command returned an invalid native error number");
|
|
92
|
+
}
|
|
93
|
+
const failure = Object.assign(new Error(response.message), { code: response.code });
|
|
94
|
+
if (response.errno !== undefined) {
|
|
95
|
+
Object.defineProperty(failure, "errno", { value: response.errno, enumerable: true, configurable: true, writable: true });
|
|
96
|
+
}
|
|
97
|
+
throw failure;
|
|
89
98
|
}
|
|
90
99
|
if (operation === "create" && (!record(response.result) || response.result.created !== true || !isFullIdentity(response.result.identity))) {
|
|
91
100
|
unverified("Windows security command did not verify private-directory creation");
|
|
92
101
|
}
|
|
93
102
|
return response.result;
|
|
94
103
|
}
|
|
104
|
+
function explainHelperFailure(error, file) {
|
|
105
|
+
if (file.endsWith("windows-security-helper.exe")) {
|
|
106
|
+
explainWindowsSecurityHelperFailure(error);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
95
109
|
function executeSync(operation, params) {
|
|
110
|
+
assertWindowsHelperAllowed();
|
|
96
111
|
const { file, args, env } = command(operation, params);
|
|
97
112
|
const startedAt = performance.now();
|
|
98
113
|
const result = spawnSync(file, args, {
|
|
@@ -107,6 +122,7 @@ function executeSync(operation, params) {
|
|
|
107
122
|
killed: result.error?.code === "ETIMEDOUT",
|
|
108
123
|
...(operation === "create" ? { creationOutcome: "unconfirmed" } : {}),
|
|
109
124
|
});
|
|
125
|
+
explainHelperFailure(error, file);
|
|
110
126
|
if (operation === "create")
|
|
111
127
|
Object.assign(error, { creationOutcome: "unconfirmed" });
|
|
112
128
|
throw error;
|
|
@@ -120,6 +136,8 @@ class WindowsSecurityCommandError extends PermissionCommandError {
|
|
|
120
136
|
constructor(file, durationMs, receipt, timedOut, timeoutMs = DEFAULT_PERMISSION_EXEC_TIMEOUT_MS) {
|
|
121
137
|
super(file, durationMs, receipt, timeoutMs);
|
|
122
138
|
this.timedOut = timedOut;
|
|
139
|
+
if (!timedOut)
|
|
140
|
+
explainHelperFailure(this, file);
|
|
123
141
|
this.creationOutcome = receipt.creationOutcome;
|
|
124
142
|
this.processExitConfirmed = receipt.processExitConfirmed;
|
|
125
143
|
if (timedOut)
|
|
@@ -162,6 +180,7 @@ export function hasUnsettledWindowsSecurityCommand(error) {
|
|
|
162
180
|
}
|
|
163
181
|
function ignoreLateError() { }
|
|
164
182
|
async function execute(operation, params, request) {
|
|
183
|
+
assertWindowsHelperAllowed();
|
|
165
184
|
const selected = request ?? command(operation, params);
|
|
166
185
|
const { file, args, env, input, timeoutMs = DEFAULT_PERMISSION_EXEC_TIMEOUT_MS, maxOutputBytes = MAX_OUTPUT_BYTES } = selected;
|
|
167
186
|
const startedAt = performance.now();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { NativeWindowsSecurityFacts } from "./native-binding.js";
|
|
2
|
-
export
|
|
2
|
+
export declare const APP_CONTAINER_SID_RE: RegExp;
|
|
3
|
+
export type DescriptorFacts = Pick<NativeWindowsSecurityFacts, "ownerSid" | "currentUserSid" | "currentAppContainerSid" | "daclPresent" | "isLocal" | "aceListComplete" | "unsupportedAceTypes" | "aces">;
|
|
3
4
|
export declare function unverified(message: string, cause?: unknown): never;
|
|
4
5
|
/** Validate policy-free observations crossing the isolated batch transport. */
|
|
5
6
|
export declare function parseWindowsOwnerAndDaclFacts(value: unknown): DescriptorFacts;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FsSafeError } from "./errors.js";
|
|
2
2
|
const SID_RE = /^s-\d+-\d+(?:-\d+)+$/;
|
|
3
|
+
export const APP_CONTAINER_SID_RE = /^s-1-15-2(?:-\d+){7,}$/;
|
|
3
4
|
const SYSTEM_SID = "s-1-5-18";
|
|
4
5
|
const ADMINISTRATORS_SID = "s-1-5-32-544";
|
|
5
6
|
const WORLD_SIDS = new Set([
|
|
@@ -34,6 +35,7 @@ function validateAce(value, allowUnknownFlags) {
|
|
|
34
35
|
function validateDescriptor(value, allowUnknownFlags) {
|
|
35
36
|
if (!record(value) || typeof value.ownerSid !== "string" || !SID_RE.test(value.ownerSid) ||
|
|
36
37
|
typeof value.currentUserSid !== "string" || !SID_RE.test(value.currentUserSid) ||
|
|
38
|
+
(value.currentAppContainerSid != null && (typeof value.currentAppContainerSid !== "string" || !APP_CONTAINER_SID_RE.test(value.currentAppContainerSid))) ||
|
|
37
39
|
typeof value.daclPresent !== "boolean" || typeof value.isLocal !== "boolean" ||
|
|
38
40
|
typeof value.aceListComplete !== "boolean" || !Array.isArray(value.unsupportedAceTypes) ||
|
|
39
41
|
!value.unsupportedAceTypes.every(uint32) || !Array.isArray(value.aces) ||
|
|
@@ -59,6 +61,8 @@ function summarize(facts) {
|
|
|
59
61
|
let groupReadable = false;
|
|
60
62
|
let groupWritable = false;
|
|
61
63
|
const trusted = new Set([facts.currentUserSid, SYSTEM_SID, ADMINISTRATORS_SID]);
|
|
64
|
+
if (facts.currentAppContainerSid)
|
|
65
|
+
trusted.add(facts.currentAppContainerSid);
|
|
62
66
|
for (const ace of facts.aces) {
|
|
63
67
|
if (ace.aceType !== "allow" || ace.flags.inheritOnly || trusted.has(ace.sid))
|
|
64
68
|
continue;
|
|
Binary file
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
export function windowsSecurityHelperPath() {
|
|
3
|
+
return fileURLToPath(new URL("./windows-security-helper.exe", import.meta.url));
|
|
4
|
+
}
|
|
5
|
+
export function explainWindowsSecurityHelperFailure(error) {
|
|
6
|
+
error.message += "; Windows security helper could not complete; rebuild with pnpm build:windows-helper or reinstall @openclaw/fs-safe";
|
|
7
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Test-only handle that omits delete sharing. */
|
|
2
|
+
export declare function holdWindowsSharingLock(filePath: string): Disposable & {
|
|
3
|
+
close(): void;
|
|
4
|
+
};
|
|
5
|
+
/** Test-only attribute updates preserve unspecified bits on the opened object. */
|
|
6
|
+
export declare function setWindowsFileAttributes(filePath: string, attrs: {
|
|
7
|
+
readOnly?: boolean;
|
|
8
|
+
hidden?: boolean;
|
|
9
|
+
system?: boolean;
|
|
10
|
+
}): void;
|
|
11
|
+
/** Test-only physical mapping reader; lcn -1 represents sparse clusters. */
|
|
12
|
+
export declare function readWindowsFileExtents(filePath: string): {
|
|
13
|
+
vcn: bigint;
|
|
14
|
+
lcn: bigint;
|
|
15
|
+
clusters: bigint;
|
|
16
|
+
}[];
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { FsSafeError } from "./errors.js";
|
|
3
|
+
import { requireNativeBinding } from "./native.js";
|
|
4
|
+
function call(operation) {
|
|
5
|
+
if (process.platform !== "win32") {
|
|
6
|
+
throw new FsSafeError("unsupported-platform", "Windows test fixtures require Windows");
|
|
7
|
+
}
|
|
8
|
+
try {
|
|
9
|
+
return operation();
|
|
10
|
+
}
|
|
11
|
+
catch (cause) {
|
|
12
|
+
if (cause instanceof FsSafeError)
|
|
13
|
+
throw cause;
|
|
14
|
+
throw new FsSafeError("helper-failed", "Windows test fixture operation failed", { cause });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** Test-only handle that omits delete sharing. */
|
|
18
|
+
export function holdWindowsSharingLock(filePath) {
|
|
19
|
+
return call(() => {
|
|
20
|
+
const native = requireNativeBinding();
|
|
21
|
+
if (!native.holdWindowsSharingLock)
|
|
22
|
+
throw new FsSafeError("helper-unavailable", "Windows sharing fixture unavailable");
|
|
23
|
+
const owner = native.holdWindowsSharingLock(path.toNamespacedPath(filePath));
|
|
24
|
+
let closed = false;
|
|
25
|
+
const close = () => {
|
|
26
|
+
if (closed)
|
|
27
|
+
return;
|
|
28
|
+
closed = true;
|
|
29
|
+
call(() => owner.close());
|
|
30
|
+
};
|
|
31
|
+
return Object.freeze({ close, [Symbol.dispose]: close });
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/** Test-only attribute updates preserve unspecified bits on the opened object. */
|
|
35
|
+
export function setWindowsFileAttributes(filePath, attrs) {
|
|
36
|
+
call(() => {
|
|
37
|
+
const native = requireNativeBinding();
|
|
38
|
+
if (!native.setWindowsFileAttributes)
|
|
39
|
+
throw new FsSafeError("helper-unavailable", "Windows attribute fixture unavailable");
|
|
40
|
+
native.setWindowsFileAttributes(path.toNamespacedPath(filePath), attrs);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/** Test-only physical mapping reader; lcn -1 represents sparse clusters. */
|
|
44
|
+
export function readWindowsFileExtents(filePath) {
|
|
45
|
+
return call(() => {
|
|
46
|
+
const native = requireNativeBinding();
|
|
47
|
+
if (!native.readWindowsFileExtents)
|
|
48
|
+
throw new FsSafeError("helper-unavailable", "Windows extent fixture unavailable");
|
|
49
|
+
return native.readWindowsFileExtents(path.toNamespacedPath(filePath));
|
|
50
|
+
});
|
|
51
|
+
}
|
|
@@ -31,16 +31,18 @@ export async function writeAllToFile(target, data, options = {}) {
|
|
|
31
31
|
const length = Math.min(WRITE_CHUNK_BYTES, buffer.byteLength - offset);
|
|
32
32
|
const position = options.position === undefined ? null : options.position + offset;
|
|
33
33
|
options.assertBeforeMutation?.();
|
|
34
|
-
const written = typeof target === "number"
|
|
35
|
-
?
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
34
|
+
const written = typeof target === "number" && options.synchronous
|
|
35
|
+
? fs.writeSync(target, buffer, offset, length, position)
|
|
36
|
+
: typeof target === "number"
|
|
37
|
+
? await new Promise((resolve, reject) => {
|
|
38
|
+
fs.write(target, buffer, offset, length, position, (error, bytesWritten) => {
|
|
39
|
+
if (error)
|
|
40
|
+
reject(error);
|
|
41
|
+
else
|
|
42
|
+
resolve(bytesWritten);
|
|
43
|
+
});
|
|
44
|
+
})
|
|
45
|
+
: (await target.write(buffer, offset, length, position)).bytesWritten;
|
|
44
46
|
if (written <= 0) {
|
|
45
47
|
throw new FsSafeError("helper-failed", "file write made no progress");
|
|
46
48
|
}
|
|
@@ -79,11 +79,12 @@ concurrent namespace changes.
|
|
|
79
79
|
| `sameFileContentsSync`, `SameFileContentsOptions` | [Exact file comparison](file-contents.md) | Compare borrowed regular-file descriptors byte for byte through EOF with bounded memory and an optional per-file byte limit, preserving both cursors and lifetimes. |
|
|
80
80
|
| `overwriteFileHandle`, `OverwriteFileHandleOptions` | [in-place-write.md](in-place-write.md) | Overwrite a borrowed read/write handle with prefix-only preparation and best-effort rollback; preserves its inode, cursor, and caller-owned lifetime. |
|
|
81
81
|
| `openRootFile`, `openRootFileSync`, `canUseRootFileOpen`, `matchRootFileOpenFailure`, related types | – | Low-level root-bounded open; rejects every symlink component by default, with `symlinks: "follow-parents-within-root"` for contained parent aliases or `"follow-within-root"` for final links too. |
|
|
82
|
+
| `createRootFileOpenBatchSync`, `RootFileOpenBatchSync` | [Synchronous open batches](reading.md#synchronous-open-batches) | Reuse native Windows root handles for synchronous file opens while retaining fresh file checks. |
|
|
82
83
|
| `appendRegularFile`, `appendRegularFileSync`, `readRegularFile`, `readRegularFileSync`, `statRegularFile`, `statRegularFileSync`, `resolveRegularFileAppendFlags`, `AppendRegularFileOptions`, `RegularFileStatResult` | [regular-file.md](regular-file.md) | Type-checked regular-file I/O. |
|
|
83
84
|
| `retainEntryForPublication`, `RetainedEntryPublication`, `RetainEntryForPublicationOptions`, `EntryPublicationReceipt`, `EntryPublicationResult`, `EntryPublicationIssue`, `PublicationIdentity`, `PublicationParent` | [One-way entry publication](entry-publication.md) | Native no-replace directory/regular-file/symlink export, including Windows NTFS junctions, under caller-exclusive namespace; explicit transition receipts, close-only disposal, no source CAS. |
|
|
84
85
|
| `retainFileInDirectory`, `RetainedFile`, related receipt/result types | [Retained Windows files](retained-file.md) | Existing-file native handle custody and explicit removal; local NTFS, producer authority required, no persistence guarantee. |
|
|
85
86
|
| `copyRootFileSync`, `createRootFileCopyBatchSync`, `RootFileCopyBatchSync`, `CopyRootFileSyncOptions`, `CopiedRootFileSync` | [copy.md](copy.md#synchronous-guarded-file-copies) | Synchronous guarded exclusive file copy, optionally cloned and bound to an expected source identity, returning an owned destination descriptor and both identities. |
|
|
86
|
-
| `sameFileIdentity`, `FileIdentityStat` | – | Compare two stats for same-inode equality. |
|
|
87
|
+
| `sameFileIdentity`, `FileIdentityStat` | – | Compare two stats for same-inode equality; unsafe Number components never match. Use bigint stats for exact Windows IDs. |
|
|
87
88
|
| `readDirectoryIdentity`, `assertDirectoryIdentitySync`, `DirectoryIdentity` | [directory-identity.md](directory-identity.md) | Observe exact bigint directory identity and synchronously check a caller-selected path, optionally retaining its canonical path. |
|
|
88
89
|
| `pathExists`, `pathExistsSync` | – | Boolean existence check that does not throw on `ENOENT`. |
|
|
89
90
|
| `assertNoSymlinkParents`, `assertNoSymlinkParentsSync`, `AssertNoSymlinkParentsOptions` | – | Reject paths whose ancestor chain contains symlinks, inspecting raw segments before `..` normalization. A `..` may undo an inspected real directory, but cannot leave the root or undo an allowed root-child symlink. Raw paths outside the root that normalize inside are rejected. |
|
|
@@ -185,6 +185,15 @@ policy to the complete archive, including entries that extraction would strip
|
|
|
185
185
|
or skip. Known UTF-8 and Unicode Path names are checked during physical admission;
|
|
186
186
|
legacy names retain backend-selected decoding and undergo the same collision
|
|
187
187
|
check before callbacks or selected bytes are returned.
|
|
188
|
+
Native legacy decoding is CP437 when neither the UTF-8 flag nor a valid Unicode
|
|
189
|
+
Path override declares UTF-8, even if the raw bytes also happen to form valid
|
|
190
|
+
UTF-8. Invalid flagged UTF-8 and invalid Unicode Path CRC/encoding fail closed;
|
|
191
|
+
the native metadata boundary enforces these checks before decoder normalization.
|
|
192
|
+
Native ZIP inspection also retains rejection of malformed Unicode Comment
|
|
193
|
+
checksums and UTF-8, including in entries unrelated to a requested payload.
|
|
194
|
+
Comment CRCs follow the native decoder's historical decoded-comment semantics;
|
|
195
|
+
valid sequential Unicode overrides retain their decoder order. Public physical
|
|
196
|
+
admission continues to reject duplicate critical Unicode Path fields.
|
|
188
197
|
|
|
189
198
|
ZIP end-record admission searches the bounded comment window for signatures
|
|
190
199
|
while retaining complete comment-length and ambiguity checks. Dense signature
|
|
@@ -244,6 +253,9 @@ remaining components are skipped before the filter callback, but still count
|
|
|
244
253
|
toward `maxEntries` and undergo traversal validation. JavaScript TAR extraction
|
|
245
254
|
copies the admitted payload range to this accepted output path, so depth checks,
|
|
246
255
|
collision checks, writes, and mode application agree.
|
|
256
|
+
ZIP directories naming the relative archive root (for example `./`) follow the
|
|
257
|
+
same skip rule on both backends. Portable preflight retains their relative `./`
|
|
258
|
+
spelling; a literal absolute `/` entry is still rejected.
|
|
247
259
|
|
|
248
260
|
An `entryFilter` sees the validated **canonical effective archive path before
|
|
249
261
|
stripping**, entry kind, and declared size. On every JavaScript and native
|
|
@@ -348,6 +360,10 @@ A failure before publication preserves a pre-existing file, and rejection does
|
|
|
348
360
|
not grant authority to delete a substituted file or alias. Failed extraction does not
|
|
349
361
|
restore overwritten contents. Active destination mutations and their guarded
|
|
350
362
|
cleanup still finish before rejection; no later destination mutation begins.
|
|
363
|
+
On supported native POSIX paths, each active destination ancestor uses its retained
|
|
364
|
+
descriptor plus fresh named-identity and physical-path observations at every guard
|
|
365
|
+
boundary. These checks are never cached or reduced to the deepest directory alone.
|
|
366
|
+
Other paths retain the full pathname checks.
|
|
351
367
|
Portable ZIP output is not eligible for publication until its stream closes or
|
|
352
368
|
the defensive `FileHandle` close succeeds. If that fallback close rejects,
|
|
353
369
|
`extractArchive()` propagates the error and publishes no entry from the staged
|
|
@@ -12,10 +12,13 @@ import {
|
|
|
12
12
|
configureFsSafeNative,
|
|
13
13
|
configureFsSafeLocks,
|
|
14
14
|
getFsSafeNativeConfig,
|
|
15
|
+
getFsSafeNativeStatus,
|
|
15
16
|
getFsSafeLockConfig,
|
|
16
17
|
type FsSafeLockConfig,
|
|
17
18
|
type FsSafeNativeConfig,
|
|
18
19
|
type FsSafeNativeMode,
|
|
20
|
+
type FsSafeNativeStatus,
|
|
21
|
+
type FsSafeNativeStatusReason,
|
|
19
22
|
} from "@openclaw/fs-safe/config";
|
|
20
23
|
```
|
|
21
24
|
|
|
@@ -42,7 +45,7 @@ Set the process-global loading policy. Configure once at startup, before the fir
|
|
|
42
45
|
| `require` | Operations that need the binding raise `FsSafeError("helper-unavailable")` when it cannot load. |
|
|
43
46
|
|
|
44
47
|
Fallbacks include guarded JavaScript, the bundled TAR/gzip WASM parser, and the
|
|
45
|
-
[packaged Windows security
|
|
48
|
+
[packaged Windows security helper](install.md#windows-security-fallback).
|
|
46
49
|
Windows command fallbacks remain subject to normal system execution policy.
|
|
47
50
|
|
|
48
51
|
## `getFsSafeNativeConfig()`
|
|
@@ -53,6 +56,73 @@ function getFsSafeNativeConfig(): FsSafeNativeConfig;
|
|
|
53
56
|
|
|
54
57
|
Return the effective configuration: programmatic overrides win, then env vars, then the package default (`auto`).
|
|
55
58
|
|
|
59
|
+
## `getFsSafeNativeStatus(options?)`
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
function getFsSafeNativeStatus(options?: { probe?: boolean }): Promise<FsSafeNativeStatus>;
|
|
63
|
+
|
|
64
|
+
type FsSafeNativeStatusReason =
|
|
65
|
+
| "not-probed" | "loaded" | "disabled" | "unsupported-target"
|
|
66
|
+
| "package-missing" | "load-failed" | "capability-missing";
|
|
67
|
+
|
|
68
|
+
type FsSafeNativeStatus = {
|
|
69
|
+
mode: FsSafeNativeMode;
|
|
70
|
+
modeSource: "programmatic configuration" | "FS_SAFE_NATIVE_MODE" | "OPENCLAW_FS_SAFE_NATIVE_MODE" | "default";
|
|
71
|
+
reason: FsSafeNativeStatusReason;
|
|
72
|
+
loaded: boolean;
|
|
73
|
+
target: string;
|
|
74
|
+
packageName?: string;
|
|
75
|
+
packageVersion?: string;
|
|
76
|
+
loadErrorCode?: string;
|
|
77
|
+
guidance?: string;
|
|
78
|
+
};
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
| Field | Meaning |
|
|
82
|
+
|---|---|
|
|
83
|
+
| `mode` | Effective `auto`, `off`, or `require` policy. |
|
|
84
|
+
| `modeSource` | The winning programmatic setting, environment variable **name**, or default. |
|
|
85
|
+
| `reason` | Cached loader outcome; `not-probed` until a load is attempted, unless the effective mode is `off` (`disabled`). |
|
|
86
|
+
| `loaded` | `true` exactly when `reason` is `loaded`. This confirms the loader's core capability check, not every operation's capabilities or kernel-atomic behavior. |
|
|
87
|
+
| `target` | Runtime platform/architecture/libc, such as `win32-x64-msvc` or `linux-x64-gnu`; also supplied for unsupported targets. |
|
|
88
|
+
| `packageName` | Expected native platform package; absent for unsupported targets. |
|
|
89
|
+
| `packageVersion` | Exact pinned version from fs-safe's manifest, when a platform package exists. |
|
|
90
|
+
| `loadErrorCode` | Present only for `load-failed`: an allowlisted loader code, or `UNKNOWN`. |
|
|
91
|
+
| `guidance` | Cause-specific next steps, shared with fallback warnings; absent for `not-probed` and `loaded`. |
|
|
92
|
+
|
|
93
|
+
The function returns a promise and loads its status module on the first call,
|
|
94
|
+
keeping the `config` import lightweight. The default, `{ probe: false }`, reads
|
|
95
|
+
the cached outcome without loading the addon. `{ probe: true }` attempts the same
|
|
96
|
+
lazy load as a native operation, unless
|
|
97
|
+
the mode is `off`. It never changes the configured mode or performs filesystem
|
|
98
|
+
mutations. Failed loads are reported even in `require` mode, without throwing.
|
|
99
|
+
`package-missing` identifies an unresolved platform package; a missing dependency
|
|
100
|
+
inside an installed package is `load-failed`. `capability-missing` means the
|
|
101
|
+
loader's core required capability is absent. Individual operations can still
|
|
102
|
+
lack capabilities when this API reports `loaded`.
|
|
103
|
+
|
|
104
|
+
For a doctor or startup health check:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import { getFsSafeNativeStatus } from "@openclaw/fs-safe/config";
|
|
108
|
+
|
|
109
|
+
const native = await getFsSafeNativeStatus({ probe: true });
|
|
110
|
+
if (native.loaded) {
|
|
111
|
+
console.log(`Native filesystem support loaded for ${native.target}`);
|
|
112
|
+
} else {
|
|
113
|
+
console.log(`Native filesystem support: ${native.reason}`);
|
|
114
|
+
if (native.guidance) console.log(native.guidance);
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Status is per module instance. A real Worker has its own configuration, loader
|
|
119
|
+
cache, and status; checking the main thread does not probe its Workers. Restart
|
|
120
|
+
the process or Worker after repairing an installation: successes and failures
|
|
121
|
+
are cached, and neither repeated probes nor mode changes retry a failed load.
|
|
122
|
+
No field contains filesystem paths, raw native error messages, or environment
|
|
123
|
+
variable values. See [fallback troubleshooting](native-helper.md#troubleshooting-fallback-warnings)
|
|
124
|
+
for the individual causes and next steps.
|
|
125
|
+
|
|
56
126
|
## `configureFsSafeLocks(config)`
|
|
57
127
|
|
|
58
128
|
```ts
|