@openclaw/openshell-sandbox 2026.9.6 → 2026.9.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/icon.png +0 -0
- package/dist/index.js +75 -229
- package/node_modules/@openclaw/fs-safe/CHANGELOG.md +102 -0
- package/node_modules/@openclaw/fs-safe/README.md +24 -6
- package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +6 -1
- package/node_modules/@openclaw/fs-safe/dist/advanced.js +3 -1
- package/node_modules/@openclaw/fs-safe/dist/archive-durability.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/archive-merge.js +1 -2
- package/node_modules/@openclaw/fs-safe/dist/archive-plan.d.ts +2 -7
- package/node_modules/@openclaw/fs-safe/dist/archive-read.js +9 -18
- package/node_modules/@openclaw/fs-safe/dist/archive-staging.d.ts +1 -2
- package/node_modules/@openclaw/fs-safe/dist/archive-staging.js +3 -3
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-directory.js +4 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-entry.d.ts +11 -11
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-entry.js +3 -35
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-integrity.d.ts +2 -2
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-integrity.js +2 -12
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-loader.d.ts +7 -3
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-loader.js +13 -10
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-manifest.js +3 -1
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-preflight.d.ts +2 -3
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-preflight.js +16 -8
- package/node_modules/@openclaw/fs-safe/dist/archive.d.ts +6 -3
- package/node_modules/@openclaw/fs-safe/dist/archive.js +22 -19
- package/node_modules/@openclaw/fs-safe/dist/atomic.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/copy-publication.d.ts +1 -3
- package/node_modules/@openclaw/fs-safe/dist/copy-publication.js +2 -6
- package/node_modules/@openclaw/fs-safe/dist/deny-mutation-match.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/deny-mutation-match.js +71 -0
- package/node_modules/@openclaw/fs-safe/dist/deny-mutations.js +4 -3
- package/node_modules/@openclaw/fs-safe/dist/directory-guard.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/directory-guard.js +0 -3
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.d.ts +20 -2
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.js +77 -1
- package/node_modules/@openclaw/fs-safe/dist/directory-receipt.js +5 -7
- package/node_modules/@openclaw/fs-safe/dist/effective-uid.js +1 -4
- package/node_modules/@openclaw/fs-safe/dist/errors.d.ts +3 -1
- package/node_modules/@openclaw/fs-safe/dist/errors.js +3 -2
- package/node_modules/@openclaw/fs-safe/dist/file-identity.js +10 -2
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-acquisition.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-admission.js +5 -1
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-held.d.ts +4 -11
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-held.js +1 -4
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-io.d.ts +3 -6
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-io.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-options.d.ts +2 -10
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root.d.ts +2 -4
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/file-store-boundary.d.ts +3 -7
- package/node_modules/@openclaw/fs-safe/dist/file-store-boundary.js +7 -10
- package/node_modules/@openclaw/fs-safe/dist/file-store-prune.js +7 -6
- package/node_modules/@openclaw/fs-safe/dist/file-store.d.ts +4 -7
- package/node_modules/@openclaw/fs-safe/dist/file-store.js +19 -21
- package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +23 -31
- package/node_modules/@openclaw/fs-safe/dist/guest.js +21 -12
- package/node_modules/@openclaw/fs-safe/dist/json-document-store.d.ts +4 -9
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-ownership.d.ts +1 -5
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue.js +2 -7
- package/node_modules/@openclaw/fs-safe/dist/local-file-access.js +2 -5
- package/node_modules/@openclaw/fs-safe/dist/local-file-descriptor.d.ts +2 -5
- package/node_modules/@openclaw/fs-safe/dist/local-roots.d.ts +2 -7
- package/node_modules/@openclaw/fs-safe/dist/move-path-cleanup.js +4 -4
- package/node_modules/@openclaw/fs-safe/dist/mutation-authority.js +5 -0
- package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +51 -14
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +8 -2
- package/node_modules/@openclaw/fs-safe/dist/native-staged-symlink.d.ts +13 -0
- package/node_modules/@openclaw/fs-safe/dist/native-staged-symlink.js +303 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch-worker.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch-worker.js +54 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch.d.ts +5 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch.js +64 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl.js +3 -0
- package/node_modules/@openclaw/fs-safe/dist/path.js +17 -1
- package/node_modules/@openclaw/fs-safe/dist/permission-exec.d.ts +6 -0
- package/node_modules/@openclaw/fs-safe/dist/permission-exec.js +3 -6
- package/node_modules/@openclaw/fs-safe/dist/permissions-public.d.ts +3 -1
- package/node_modules/@openclaw/fs-safe/dist/permissions-public.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/permissions-windows.d.ts +3 -6
- package/node_modules/@openclaw/fs-safe/dist/permissions.d.ts +3 -10
- package/node_modules/@openclaw/fs-safe/dist/permissions.js +0 -1
- package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-admission.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-admission.js +4 -2
- package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-shared-route.d.ts +2 -8
- package/node_modules/@openclaw/fs-safe/dist/pinned-open.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/pinned-open.js +2 -3
- package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +4 -0
- package/node_modules/@openclaw/fs-safe/dist/read-opened-file.d.ts +2 -5
- package/node_modules/@openclaw/fs-safe/dist/regular-file.js +3 -3
- package/node_modules/@openclaw/fs-safe/dist/replace-directory.js +5 -17
- package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.js +36 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.d.ts +4 -10
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.js +66 -38
- package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.d.ts +7 -11
- package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +10 -2
- package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.d.ts +21 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.js +123 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-mutation.d.ts +26 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-mutation.js +47 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.d.ts +2 -2
- package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.js +16 -4
- package/node_modules/@openclaw/fs-safe/dist/replace-file-types.d.ts +55 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file.d.ts +3 -55
- package/node_modules/@openclaw/fs-safe/dist/replace-file.js +31 -11
- package/node_modules/@openclaw/fs-safe/dist/retained-directory-replacement.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/retained-directory-replacement.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/retained-file-types.d.ts +50 -0
- package/node_modules/@openclaw/fs-safe/dist/retained-file-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/retained-file.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/retained-file.js +121 -0
- package/node_modules/@openclaw/fs-safe/dist/root-context.js +3 -2
- package/node_modules/@openclaw/fs-safe/dist/root-directory-entry.d.ts +9 -0
- package/node_modules/@openclaw/fs-safe/dist/root-directory-entry.js +28 -0
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.d.ts +9 -1
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +88 -37
- package/node_modules/@openclaw/fs-safe/dist/root-file-final-admission.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/root-file-final-admission.js +1 -6
- package/node_modules/@openclaw/fs-safe/dist/root-file.js +2 -5
- package/node_modules/@openclaw/fs-safe/dist/root-handle-context.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/root-handle-context.js +12 -0
- package/node_modules/@openclaw/fs-safe/dist/root-impl.d.ts +3 -3
- package/node_modules/@openclaw/fs-safe/dist/root-impl.js +8 -5
- package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.d.ts +2 -7
- package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +3 -3
- package/node_modules/@openclaw/fs-safe/dist/root-observed-path.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/root-observed-path.js +0 -3
- package/node_modules/@openclaw/fs-safe/dist/root-path-observation.d.ts +4 -11
- package/node_modules/@openclaw/fs-safe/dist/root-path.js +7 -10
- package/node_modules/@openclaw/fs-safe/dist/root-paths.d.ts +2 -6
- package/node_modules/@openclaw/fs-safe/dist/root-remove-identity.d.ts +1 -3
- package/node_modules/@openclaw/fs-safe/dist/root-walk.d.ts +19 -12
- package/node_modules/@openclaw/fs-safe/dist/root-walk.js +56 -20
- package/node_modules/@openclaw/fs-safe/dist/root-write-admission.js +1 -6
- package/node_modules/@openclaw/fs-safe/dist/root-write-complete-parent.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/root-write-complete-parent.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.js +8 -2
- package/node_modules/@openclaw/fs-safe/dist/secret-file.d.ts +6 -2
- package/node_modules/@openclaw/fs-safe/dist/secret-file.js +12 -16
- package/node_modules/@openclaw/fs-safe/dist/secret-read-policy.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/secure-file-windows.js +5 -14
- package/node_modules/@openclaw/fs-safe/dist/secure-file.js +3 -2
- package/node_modules/@openclaw/fs-safe/dist/secure-temp-dir.js +2 -7
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js +4 -4
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-admission-parser.d.ts +1 -2
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-handle.d.ts +2 -8
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-policy.d.ts +2 -7
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.d.ts +2 -2
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js +11 -12
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-stale-admission.d.ts +1 -5
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock.js +9 -7
- package/node_modules/@openclaw/fs-safe/dist/staged-symlink-types.d.ts +39 -0
- package/node_modules/@openclaw/fs-safe/dist/staged-symlink-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/stat-observation.js +3 -10
- package/node_modules/@openclaw/fs-safe/dist/strict-file-identity.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/strict-file-identity.js +6 -6
- package/node_modules/@openclaw/fs-safe/dist/symlink-parents.js +58 -7
- package/node_modules/@openclaw/fs-safe/dist/temp-target.js +8 -10
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-admission.js +28 -33
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.js +16 -17
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-descriptor.js +1 -2
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-owner.js +4 -9
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.js +52 -0
- package/node_modules/@openclaw/fs-safe/dist/test-hooks.d.ts +5 -1
- package/node_modules/@openclaw/fs-safe/dist/text-atomic.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/text-atomic.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/trash.js +27 -1
- package/node_modules/@openclaw/fs-safe/dist/walk.d.ts +2 -5
- package/node_modules/@openclaw/fs-safe/dist/watch-alias.d.ts +6 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-alias.js +88 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-hints.d.ts +9 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-hints.js +93 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-native.d.ts +36 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-native.js +73 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-scan.d.ts +28 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-scan.js +300 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-stream.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-stream.js +32 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-types.d.ts +60 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/watch.d.ts +5 -0
- package/node_modules/@openclaw/fs-safe/dist/watch.js +530 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-owner.d.ts +3 -7
- package/node_modules/@openclaw/fs-safe/dist/windows-owner.js +0 -1
- package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.cs +6 -6
- package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.ps1 +78 -3
- package/node_modules/@openclaw/fs-safe/dist/windows-security-command.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-security-command.js +66 -15
- package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.js +6 -2
- package/node_modules/@openclaw/fs-safe/docs/advanced.md +4 -2
- package/node_modules/@openclaw/fs-safe/docs/archive.md +16 -0
- package/node_modules/@openclaw/fs-safe/docs/atomic.md +93 -3
- package/node_modules/@openclaw/fs-safe/docs/contributing.md +102 -4
- package/node_modules/@openclaw/fs-safe/docs/copy.md +2 -0
- package/node_modules/@openclaw/fs-safe/docs/durability.md +7 -0
- package/node_modules/@openclaw/fs-safe/docs/file-store.md +5 -0
- package/node_modules/@openclaw/fs-safe/docs/guest.md +7 -1
- package/node_modules/@openclaw/fs-safe/docs/index.md +1 -0
- package/node_modules/@openclaw/fs-safe/docs/install.md +30 -0
- package/node_modules/@openclaw/fs-safe/docs/native-helper.md +20 -4
- package/node_modules/@openclaw/fs-safe/docs/native.md +63 -1
- package/node_modules/@openclaw/fs-safe/docs/permissions.md +66 -0
- package/node_modules/@openclaw/fs-safe/docs/public-api.md +30 -3
- package/node_modules/@openclaw/fs-safe/docs/retained-file.md +115 -0
- package/node_modules/@openclaw/fs-safe/docs/root.md +35 -9
- package/node_modules/@openclaw/fs-safe/docs/secret-file.md +19 -0
- package/node_modules/@openclaw/fs-safe/docs/security-model.md +4 -1
- package/node_modules/@openclaw/fs-safe/docs/sidecar-lock.md +5 -1
- package/node_modules/@openclaw/fs-safe/docs/staged-symlink.md +124 -0
- package/node_modules/@openclaw/fs-safe/docs/store.md +3 -1
- package/node_modules/@openclaw/fs-safe/docs/temp.md +24 -4
- package/node_modules/@openclaw/fs-safe/docs/testing.md +210 -0
- package/node_modules/@openclaw/fs-safe/docs/types.md +6 -0
- package/node_modules/@openclaw/fs-safe/docs/walk.md +34 -1
- package/node_modules/@openclaw/fs-safe/docs/watch.md +251 -0
- package/node_modules/@openclaw/fs-safe/docs/writing.md +21 -0
- package/node_modules/@openclaw/fs-safe/package.json +13 -9
- package/node_modules/@openclaw/fs-safe-darwin-arm64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-darwin-arm64/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-darwin-x64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-darwin-x64/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-arm64-musl/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-arm64-musl/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-x64-gnu/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-x64-gnu/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-x64-musl/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-x64-musl/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-win32-x64-msvc/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-win32-x64-msvc/package.json +1 -1
- package/node_modules/p-limit/index.d.ts +2 -0
- package/node_modules/p-limit/index.js +17 -1
- package/node_modules/p-limit/package.json +1 -1
- package/node_modules/p-limit/readme.md +2 -0
- package/package.json +6 -6
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-owner.d.ts +0 -20
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-owner.js +0 -78
package/assets/icon.png
CHANGED
|
Binary file
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import fsSync from "node:fs";
|
|
|
5
5
|
import fsPromises from "node:fs/promises";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { movePathWithCopyFallback } from "@openclaw/fs-safe/atomic";
|
|
8
|
+
import { GUEST_FILESYSTEM_PYTHON } from "@openclaw/fs-safe/guest";
|
|
8
9
|
import { createDeferred, formatPluginConfigIssue, mapPluginConfigIssues } from "openclaw/plugin-sdk/extension-shared";
|
|
9
10
|
import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue";
|
|
10
11
|
import { FsSafeError, canonicalPathFromExistingAncestor, extractErrorCode } from "openclaw/plugin-sdk/security-runtime";
|
|
@@ -12,7 +13,7 @@ import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coer
|
|
|
12
13
|
import { buildPluginConfigSchema } from "openclaw/plugin-sdk/core";
|
|
13
14
|
import { MAX_TIMER_TIMEOUT_SECONDS } from "openclaw/plugin-sdk/number-runtime";
|
|
14
15
|
import { z } from "zod";
|
|
15
|
-
import { assertNoSymlinkParents,
|
|
16
|
+
import { assertNoSymlinkParents, isPathInside, root } from "openclaw/plugin-sdk/file-access-runtime";
|
|
16
17
|
import pLimit from "p-limit";
|
|
17
18
|
//#region extensions/openshell/src/cli.ts
|
|
18
19
|
function buildOpenShellBaseArgv(config) {
|
|
@@ -63,17 +64,6 @@ const DEFAULT_REMOTE_WORKSPACE_DIR = "/sandbox";
|
|
|
63
64
|
const DEFAULT_REMOTE_AGENT_WORKSPACE_DIR = "/agent";
|
|
64
65
|
const DEFAULT_TIMEOUT_MS = 12e4;
|
|
65
66
|
const OPEN_SHELL_MANAGED_REMOTE_ROOTS = [DEFAULT_REMOTE_WORKSPACE_DIR, DEFAULT_REMOTE_AGENT_WORKSPACE_DIR];
|
|
66
|
-
function normalizeProviders(value) {
|
|
67
|
-
const seen = /* @__PURE__ */ new Set();
|
|
68
|
-
const providers = [];
|
|
69
|
-
for (const entry of value ?? []) {
|
|
70
|
-
const normalized = entry.trim();
|
|
71
|
-
if (seen.has(normalized)) continue;
|
|
72
|
-
seen.add(normalized);
|
|
73
|
-
providers.push(normalized);
|
|
74
|
-
}
|
|
75
|
-
return providers;
|
|
76
|
-
}
|
|
77
67
|
const nonEmptyTrimmedString = (message) => z.string({ error: message }).trim().min(1, { error: message });
|
|
78
68
|
const openShellManagedRemotePath = (fieldName) => nonEmptyTrimmedString(`${fieldName} must be a non-empty string`).regex(/^\/(?:sandbox|agent)(?:\/|$)/, { error: (issue) => String(issue.input).startsWith("/") ? `OpenShell ${fieldName} must stay under /sandbox or /agent` : `OpenShell ${fieldName} must be absolute` }).refine((value) => isManagedOpenShellRemotePath(path.posix.normalize(value)), { error: `OpenShell ${fieldName} must stay under /sandbox or /agent` });
|
|
79
69
|
const openShellWorkspaceName = z.string({ error: "workspace must be a valid OpenShell workspace name" }).trim().min(1, { error: "workspace must be a valid OpenShell workspace name" }).max(19, { error: "workspace must be at most 19 characters" }).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, { error: "workspace must contain lowercase alphanumeric characters or single hyphens and must not start or end with a hyphen" });
|
|
@@ -120,22 +110,7 @@ function createOpenShellPluginConfigSchema() {
|
|
|
120
110
|
} });
|
|
121
111
|
}
|
|
122
112
|
function resolveOpenShellPluginConfig(value) {
|
|
123
|
-
|
|
124
|
-
mode: DEFAULT_MODE,
|
|
125
|
-
command: DEFAULT_COMMAND,
|
|
126
|
-
gateway: void 0,
|
|
127
|
-
gatewayEndpoint: void 0,
|
|
128
|
-
workspace: void 0,
|
|
129
|
-
from: DEFAULT_SOURCE,
|
|
130
|
-
policy: void 0,
|
|
131
|
-
providers: [],
|
|
132
|
-
gpu: false,
|
|
133
|
-
autoProviders: true,
|
|
134
|
-
remoteWorkspaceDir: DEFAULT_REMOTE_WORKSPACE_DIR,
|
|
135
|
-
remoteAgentWorkspaceDir: DEFAULT_REMOTE_AGENT_WORKSPACE_DIR,
|
|
136
|
-
timeoutMs: DEFAULT_TIMEOUT_MS
|
|
137
|
-
};
|
|
138
|
-
const parsed = OpenShellPluginConfigSchema.safeParse(value);
|
|
113
|
+
const parsed = OpenShellPluginConfigSchema.safeParse(value === void 0 ? {} : value);
|
|
139
114
|
if (!parsed.success) {
|
|
140
115
|
const message = formatPluginConfigIssue(parsed.error.issues[0]);
|
|
141
116
|
throw new Error(`Invalid openshell plugin config: ${message}`);
|
|
@@ -149,7 +124,7 @@ function resolveOpenShellPluginConfig(value) {
|
|
|
149
124
|
workspace: cfg.workspace,
|
|
150
125
|
from: cfg.from ?? DEFAULT_SOURCE,
|
|
151
126
|
policy: cfg.policy,
|
|
152
|
-
providers:
|
|
127
|
+
providers: [...new Set(cfg.providers ?? [])],
|
|
153
128
|
gpu: cfg.gpu ?? false,
|
|
154
129
|
autoProviders: cfg.autoProviders ?? true,
|
|
155
130
|
remoteWorkspaceDir: normalizeOpenShellRemotePath(cfg.remoteWorkspaceDir, DEFAULT_REMOTE_WORKSPACE_DIR, "remoteWorkspaceDir"),
|
|
@@ -196,64 +171,49 @@ var OpenShellFsBridge = class {
|
|
|
196
171
|
}
|
|
197
172
|
async readFile(params) {
|
|
198
173
|
const target = this.resolveTarget(params);
|
|
199
|
-
const hostPath =
|
|
200
|
-
let opened;
|
|
174
|
+
const hostPath = target.hostPath;
|
|
201
175
|
try {
|
|
202
|
-
await
|
|
203
|
-
target,
|
|
204
|
-
allowFinalSymlinkForUnlink: false
|
|
205
|
-
});
|
|
206
|
-
const root$1 = await root(target.mountHostRoot);
|
|
207
|
-
if (params.maxBytes !== void 0) return (await root$1.read(path.relative(target.mountHostRoot, hostPath), {
|
|
176
|
+
return (await (await root(target.mountHostRoot)).read(relativeToRoot(target, hostPath), {
|
|
208
177
|
hardlinks: "reject",
|
|
209
|
-
maxBytes: params.maxBytes
|
|
178
|
+
maxBytes: params.maxBytes ?? Infinity
|
|
210
179
|
})).buffer;
|
|
211
|
-
opened = await root$1.open(path.relative(target.mountHostRoot, hostPath), { hardlinks: "reject" });
|
|
212
|
-
try {
|
|
213
|
-
return await opened.handle.readFile();
|
|
214
|
-
} finally {
|
|
215
|
-
await opened.handle.close();
|
|
216
|
-
}
|
|
217
180
|
} catch (err) {
|
|
218
181
|
throw new Error(`Sandbox boundary checks failed; cannot read files: ${target.containerPath}`, { cause: err });
|
|
219
182
|
}
|
|
220
183
|
}
|
|
221
184
|
async readDirectory(params) {
|
|
222
185
|
const target = this.resolveTarget(params);
|
|
223
|
-
const hostPath =
|
|
224
|
-
await
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
});
|
|
228
|
-
return (await (await root(target.mountHostRoot)).list(relativeToRoot(target, hostPath), { withFileTypes: true })).map(({ name, isDirectory }) => ({
|
|
186
|
+
const hostPath = target.hostPath;
|
|
187
|
+
const root$1 = await root(target.mountHostRoot);
|
|
188
|
+
const entries = [];
|
|
189
|
+
for await (const { name, isDirectory } of root$1.entries(relativeToRoot(target, hostPath), { order: "sorted" })) entries.push({
|
|
229
190
|
name,
|
|
230
191
|
isDirectory
|
|
231
|
-
})
|
|
192
|
+
});
|
|
193
|
+
return entries;
|
|
232
194
|
}
|
|
233
195
|
async writeFile(params) {
|
|
234
196
|
const target = this.resolveTarget(params);
|
|
235
|
-
const hostPath =
|
|
197
|
+
const hostPath = target.hostPath;
|
|
236
198
|
this.ensureWritable(target, "write files");
|
|
237
|
-
await assertLocalPathSafety({
|
|
238
|
-
target,
|
|
239
|
-
allowFinalSymlinkForUnlink: false
|
|
240
|
-
});
|
|
241
199
|
const buffer = Buffer.isBuffer(params.data) ? params.data : Buffer.from(params.data, params.encoding ?? "utf8");
|
|
242
|
-
await (await root(target.mountHostRoot)).write(
|
|
200
|
+
await (await root(target.mountHostRoot)).write(relativeToRoot(target, hostPath), buffer, {
|
|
201
|
+
mkdir: params.mkdir,
|
|
202
|
+
mutationSymlinks: "reject"
|
|
203
|
+
});
|
|
243
204
|
await this.backend.syncLocalPathToRemote(hostPath, target.containerPath);
|
|
244
205
|
}
|
|
245
206
|
async createFileExclusive(params) {
|
|
246
207
|
const target = this.resolveTarget(params);
|
|
247
|
-
const hostPath =
|
|
208
|
+
const hostPath = target.hostPath;
|
|
248
209
|
this.ensureWritable(target, "create files");
|
|
249
|
-
await assertLocalPathSafety({
|
|
250
|
-
target,
|
|
251
|
-
allowFinalSymlinkForUnlink: false
|
|
252
|
-
});
|
|
253
210
|
const buffer = Buffer.isBuffer(params.data) ? params.data : Buffer.from(params.data, params.encoding ?? "utf8");
|
|
254
211
|
const root$2 = await root(target.mountHostRoot);
|
|
255
212
|
try {
|
|
256
|
-
await root$2.create(
|
|
213
|
+
await root$2.create(relativeToRoot(target, hostPath), buffer, {
|
|
214
|
+
mkdir: params.mkdir !== false,
|
|
215
|
+
mutationSymlinks: "reject"
|
|
216
|
+
});
|
|
257
217
|
} catch (error) {
|
|
258
218
|
if (error instanceof FsSafeError && error.code === "already-exists") return "exists";
|
|
259
219
|
throw error;
|
|
@@ -263,7 +223,7 @@ var OpenShellFsBridge = class {
|
|
|
263
223
|
}
|
|
264
224
|
async mkdirp(params) {
|
|
265
225
|
const target = this.resolveTarget(params);
|
|
266
|
-
const hostPath =
|
|
226
|
+
const hostPath = target.hostPath;
|
|
267
227
|
this.ensureWritable(target, "create directories");
|
|
268
228
|
await assertLocalPathSafety({
|
|
269
229
|
target,
|
|
@@ -277,7 +237,7 @@ var OpenShellFsBridge = class {
|
|
|
277
237
|
}
|
|
278
238
|
async remove(params) {
|
|
279
239
|
const target = this.resolveTarget(params);
|
|
280
|
-
const hostPath =
|
|
240
|
+
const hostPath = target.hostPath;
|
|
281
241
|
this.ensureWritable(target, "remove files", params.recursive);
|
|
282
242
|
await assertLocalPathSafety({
|
|
283
243
|
target,
|
|
@@ -297,8 +257,8 @@ var OpenShellFsBridge = class {
|
|
|
297
257
|
}
|
|
298
258
|
async rename(params) {
|
|
299
259
|
const { from, to } = this.resolveRenameTargets(params);
|
|
300
|
-
const fromHostPath =
|
|
301
|
-
const toHostPath =
|
|
260
|
+
const fromHostPath = from.hostPath;
|
|
261
|
+
const toHostPath = to.hostPath;
|
|
302
262
|
await assertLocalPathSafety({
|
|
303
263
|
target: from,
|
|
304
264
|
allowFinalSymlinkForUnlink: true
|
|
@@ -324,7 +284,7 @@ var OpenShellFsBridge = class {
|
|
|
324
284
|
}
|
|
325
285
|
async stat(params) {
|
|
326
286
|
const target = this.resolveTarget(params);
|
|
327
|
-
const hostPath =
|
|
287
|
+
const hostPath = target.hostPath;
|
|
328
288
|
const stats = await fsPromises.lstat(hostPath).catch(() => null);
|
|
329
289
|
if (!stats) return null;
|
|
330
290
|
await assertLocalPathSafety({
|
|
@@ -348,10 +308,6 @@ var OpenShellFsBridge = class {
|
|
|
348
308
|
workdir
|
|
349
309
|
})), ...this.sandbox.readOnlyResourceMounts ?? []];
|
|
350
310
|
}
|
|
351
|
-
requireHostPath(target) {
|
|
352
|
-
if (!target.hostPath) throw new Error(`OpenShell mirror bridge requires a local host path: ${target.containerPath}`);
|
|
353
|
-
return target.hostPath;
|
|
354
|
-
}
|
|
355
311
|
containerMounts(readOnlyMounts = this.readOnlyMounts()) {
|
|
356
312
|
const workspaceRoot = path.resolve(this.sandbox.workspaceDir);
|
|
357
313
|
const agentRoot = path.resolve(this.sandbox.agentWorkspaceDir);
|
|
@@ -450,7 +406,7 @@ async function removeLocalRootPath(params) {
|
|
|
450
406
|
const relativePath = relativeToRoot(params.target, params.hostPath);
|
|
451
407
|
try {
|
|
452
408
|
if (params.force === false) await fsPromises.lstat(params.hostPath);
|
|
453
|
-
const targets = params.recursive && !relativePath ? await root$3.list("") : [relativePath];
|
|
409
|
+
const targets = params.recursive && !relativePath ? (await root$3.list("")).map((name) => relativeToRoot(params.target, path.join(params.hostPath, name))) : [relativePath];
|
|
454
410
|
for (const target of targets) await root$3.remove(target, {
|
|
455
411
|
force: params.force !== false,
|
|
456
412
|
...params.recursive ? {
|
|
@@ -479,7 +435,7 @@ async function mkdirParentPath(root, relativePath) {
|
|
|
479
435
|
}
|
|
480
436
|
function relativeToRoot(target, hostPath) {
|
|
481
437
|
const relativePath = path.relative(target.mountHostRoot, hostPath);
|
|
482
|
-
return relativePath
|
|
438
|
+
return relativePath ? `.${path.sep}${relativePath}` : "";
|
|
483
439
|
}
|
|
484
440
|
async function assertRenameSourceSupported(fromHostPath) {
|
|
485
441
|
const stats = await fsPromises.lstat(fromHostPath);
|
|
@@ -513,21 +469,14 @@ async function nearestExistingDirectoryStats(params) {
|
|
|
513
469
|
return await fsPromises.lstat(rootPath);
|
|
514
470
|
}
|
|
515
471
|
function isNotFoundError(err) {
|
|
516
|
-
return err instanceof FsSafeError && err.code === "not-found" ||
|
|
472
|
+
return err instanceof FsSafeError && err.code === "not-found" || extractErrorCode(err) === "ENOENT";
|
|
517
473
|
}
|
|
518
474
|
async function assertLocalPathSafety(params) {
|
|
519
475
|
const { hostPath, mountHostRoot } = params.target;
|
|
520
|
-
if (!hostPath) throw new Error(`Missing local host path for ${params.target.containerPath}`);
|
|
521
|
-
const canonicalRoot = await fsPromises.realpath(mountHostRoot).catch((error) => {
|
|
522
|
-
if (isNotFoundError(error)) return path.resolve(mountHostRoot);
|
|
523
|
-
throw error;
|
|
524
|
-
});
|
|
525
476
|
const unlinkSymlink = params.allowFinalSymlinkForUnlink && hostPath !== mountHostRoot && (await fsPromises.lstat(hostPath).catch((error) => {
|
|
526
477
|
if (isNotFoundError(error)) return null;
|
|
527
478
|
throw error;
|
|
528
479
|
}))?.isSymbolicLink();
|
|
529
|
-
const candidate = unlinkSymlink ? path.resolve(canonicalRoot, path.relative(mountHostRoot, hostPath)) : await canonicalPathFromExistingAncestor$1(hostPath);
|
|
530
|
-
if (!isPathInside(canonicalRoot, candidate)) throw new Error(`Sandbox path escapes allowed mounts; cannot access: ${params.target.containerPath}`);
|
|
531
480
|
await assertNoSymlinkParents({
|
|
532
481
|
rootDir: mountHostRoot,
|
|
533
482
|
targetPath: unlinkSymlink ? path.dirname(hostPath) : hostPath,
|
|
@@ -619,107 +568,28 @@ function buildOpenShellDirectoryUploadArgs(params) {
|
|
|
619
568
|
];
|
|
620
569
|
}
|
|
621
570
|
const REMOTE_MANAGED_ROOTS_EMPTY_SCRIPT = "for root in \"$@\"; do if [ -d \"$root\" ] && [ -n \"$(ls -A \"$root\")\" ]; then printf \"1\\n\"; exit 0; fi; done; printf \"0\\n\"";
|
|
622
|
-
const
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
" case \"$part\" in \".\"|\"..\") die \"unsafe remote directory component: $part\" ;; esac",
|
|
645
|
-
" if [ \"$current\" = \"/\" ]; then next=\"/$part\"; else next=\"$current/$part\"; fi",
|
|
646
|
-
" if [ -L \"$next\" ]; then die \"unsafe remote directory symlink: $next\"; fi",
|
|
647
|
-
" if [ -e \"$next\" ]; then",
|
|
648
|
-
" if [ ! -d \"$next\" ]; then die \"unsafe remote directory component: $next\"; fi",
|
|
649
|
-
" else",
|
|
650
|
-
" if [ \"$create\" != \"1\" ]; then die \"remote directory not found: $next\"; fi",
|
|
651
|
-
" mkdir -- \"$next\"",
|
|
652
|
-
" fi",
|
|
653
|
-
" current=\"$next\"",
|
|
654
|
-
" done",
|
|
655
|
-
" printf \"%s\\n\" \"$current\"",
|
|
656
|
-
"}",
|
|
657
|
-
"pin_dir_or_missing() {",
|
|
658
|
-
" root=\"$1\"",
|
|
659
|
-
" relative=\"$2\"",
|
|
660
|
-
" missing_ok=\"$3\"",
|
|
661
|
-
" case \"$root\" in /*) ;; *) die \"remote root must be absolute: $root\" ;; esac",
|
|
662
|
-
" root=\"${root%/}\"",
|
|
663
|
-
" [ -n \"$root\" ] || root=\"/\"",
|
|
664
|
-
" if [ -L \"$root\" ]; then die \"unsafe remote root symlink: $root\"; fi",
|
|
665
|
-
" if [ ! -d \"$root\" ]; then",
|
|
666
|
-
" if [ -e \"$root\" ]; then die \"unsafe remote root component: $root\"; fi",
|
|
667
|
-
" if [ \"$missing_ok\" = \"1\" ]; then printf \"\\n\"; return 0; fi",
|
|
668
|
-
" die \"remote directory not found: $root\"",
|
|
669
|
-
" fi",
|
|
670
|
-
" canonical_root=\"$(cd \"$root\" && pwd -P)\"",
|
|
671
|
-
" current=\"$canonical_root\"",
|
|
672
|
-
" relative=\"${relative#/}\"",
|
|
673
|
-
" while [ -n \"$relative\" ]; do",
|
|
674
|
-
" part=\"${relative%%/*}\"",
|
|
675
|
-
" if [ \"$part\" = \"$relative\" ]; then relative=\"\"; else relative=\"${relative#*/}\"; fi",
|
|
676
|
-
" [ -n \"$part\" ] || continue",
|
|
677
|
-
" case \"$part\" in \".\"|\"..\") die \"unsafe remote directory component: $part\" ;; esac",
|
|
678
|
-
" if [ \"$current\" = \"/\" ]; then next=\"/$part\"; else next=\"$current/$part\"; fi",
|
|
679
|
-
" if [ -L \"$next\" ]; then die \"unsafe remote directory symlink: $next\"; fi",
|
|
680
|
-
" if [ -e \"$next\" ]; then",
|
|
681
|
-
" if [ ! -d \"$next\" ]; then die \"unsafe remote directory component: $next\"; fi",
|
|
682
|
-
" else",
|
|
683
|
-
" if [ \"$missing_ok\" = \"1\" ]; then printf \"\\n\"; return 0; fi",
|
|
684
|
-
" die \"remote directory not found: $next\"",
|
|
685
|
-
" fi",
|
|
686
|
-
" current=\"$next\"",
|
|
687
|
-
" done",
|
|
688
|
-
" printf \"%s\\n\" \"$current\"",
|
|
689
|
-
"}",
|
|
690
|
-
"operation=\"$1\"",
|
|
691
|
-
"case \"$operation\" in",
|
|
692
|
-
" mkdirp)",
|
|
693
|
-
" pin_dir \"$2\" \"$3\" 1 >/dev/null",
|
|
694
|
-
" ;;",
|
|
695
|
-
" remove)",
|
|
696
|
-
" validate_basename \"$4\"",
|
|
697
|
-
" parent=\"$(pin_dir_or_missing \"$2\" \"$3\" \"${5:-0}\")\"",
|
|
698
|
-
" [ -n \"$parent\" ] || exit 0",
|
|
699
|
-
" target=\"$parent/$4\"",
|
|
700
|
-
" if [ -d \"$target\" ] && [ ! -L \"$target\" ]; then rm -rf -- \"$target\"; elif [ -e \"$target\" ] || [ -L \"$target\" ]; then rm -f -- \"$target\"; fi",
|
|
701
|
-
" ;;",
|
|
702
|
-
" removefile)",
|
|
703
|
-
" validate_basename \"$4\"",
|
|
704
|
-
" parent=\"$(pin_dir_or_missing \"$2\" \"$3\" \"${5:-0}\")\"",
|
|
705
|
-
" [ -n \"$parent\" ] || exit 0",
|
|
706
|
-
" target=\"$parent/$4\"",
|
|
707
|
-
" if [ -d \"$target\" ] && [ ! -L \"$target\" ]; then rmdir -- \"$target\"; elif [ -e \"$target\" ] || [ -L \"$target\" ]; then rm -f -- \"$target\"; fi",
|
|
708
|
-
" ;;",
|
|
709
|
-
" rename)",
|
|
710
|
-
" src_parent=\"$(pin_dir \"$2\" \"$3\" 0)\"",
|
|
711
|
-
" validate_basename \"$4\"",
|
|
712
|
-
" dst_parent=\"$(pin_dir \"$5\" \"$6\" 1)\"",
|
|
713
|
-
" validate_basename \"$7\"",
|
|
714
|
-
" if [ -L \"$dst_parent/$7\" ]; then die \"unsafe remote rename target symlink: $dst_parent/$7\"; fi",
|
|
715
|
-
" if [ -d \"$dst_parent/$7\" ]; then die \"unsafe remote rename target directory: $dst_parent/$7\"; fi",
|
|
716
|
-
" mv -- \"$src_parent/$4\" \"$dst_parent/$7\"",
|
|
717
|
-
" ;;",
|
|
718
|
-
" *)",
|
|
719
|
-
" die \"unknown remote path mutation: $operation\"",
|
|
720
|
-
" ;;",
|
|
721
|
-
"esac"
|
|
722
|
-
].join("\n");
|
|
571
|
+
const OPEN_SHELL_GUEST_MUTATION_PYTHON = `
|
|
572
|
+
import os, sys
|
|
573
|
+
guest = sys.argv.pop(1)
|
|
574
|
+
ignore_missing_parent = sys.argv.pop(1) == '1'
|
|
575
|
+
operation = sys.argv[1]
|
|
576
|
+
for index in ((2, 5) if operation == 'rename' else (2,)):
|
|
577
|
+
root = sys.argv[index].rstrip('/') or '/'
|
|
578
|
+
sys.argv[index] = root
|
|
579
|
+
if not os.path.isabs(root) or os.path.islink(root):
|
|
580
|
+
raise OSError('unsafe remote root: ' + root)
|
|
581
|
+
if operation != 'remove':
|
|
582
|
+
os.makedirs(root, exist_ok=True)
|
|
583
|
+
if operation == 'rename':
|
|
584
|
+
target = os.path.join(sys.argv[5], sys.argv[6], sys.argv[7])
|
|
585
|
+
if os.path.islink(target) or os.path.isdir(target):
|
|
586
|
+
raise OSError('unsafe remote rename target: ' + target)
|
|
587
|
+
try:
|
|
588
|
+
exec(guest)
|
|
589
|
+
except FileNotFoundError:
|
|
590
|
+
if operation != 'remove' or not ignore_missing_parent:
|
|
591
|
+
raise
|
|
592
|
+
`;
|
|
723
593
|
const ENSURE_OPEN_SHELL_REMOTE_REAL_DIRECTORY_SCRIPT = [
|
|
724
594
|
"set -e",
|
|
725
595
|
"target=\"$1\"",
|
|
@@ -767,9 +637,6 @@ const ENSURE_OPEN_SHELL_REMOTE_REAL_DIRECTORY_SCRIPT = [
|
|
|
767
637
|
" current=\"$next\"",
|
|
768
638
|
"done"
|
|
769
639
|
].join("\n");
|
|
770
|
-
function buildOpenShellSshExecEnv() {
|
|
771
|
-
return sanitizeEnvVars(process.env).allowed;
|
|
772
|
-
}
|
|
773
640
|
function createOpenShellSandboxBackendFactory(params) {
|
|
774
641
|
return async (createParams) => await createOpenShellSandboxBackend({
|
|
775
642
|
...params,
|
|
@@ -868,7 +735,7 @@ var OpenShellSandboxBackendImpl = class {
|
|
|
868
735
|
});
|
|
869
736
|
return {
|
|
870
737
|
argv: pending.argv,
|
|
871
|
-
env:
|
|
738
|
+
env: sanitizeEnvVars(process.env).allowed,
|
|
872
739
|
stdinMode: "pipe-open",
|
|
873
740
|
finalizeToken: pending.token
|
|
874
741
|
};
|
|
@@ -1004,16 +871,7 @@ var OpenShellSandboxBackendImpl = class {
|
|
|
1004
871
|
}
|
|
1005
872
|
resolveWorkdirValidationRoot(workdir) {
|
|
1006
873
|
try {
|
|
1007
|
-
|
|
1008
|
-
return resolveOpenShellWorkspaceRoot([{
|
|
1009
|
-
remote: normalizeRemotePath(this.params.remoteWorkspaceDir),
|
|
1010
|
-
owner: "workspace",
|
|
1011
|
-
value: void 0
|
|
1012
|
-
}, {
|
|
1013
|
-
remote: normalizeRemotePath(this.params.remoteAgentWorkspaceDir),
|
|
1014
|
-
owner: "agent",
|
|
1015
|
-
value: void 0
|
|
1016
|
-
}], normalized)?.remote ?? this.params.remoteWorkspaceDir;
|
|
874
|
+
return this.resolveRemoteTarget(workdir).root;
|
|
1017
875
|
} catch {
|
|
1018
876
|
return this.params.remoteWorkspaceDir;
|
|
1019
877
|
}
|
|
@@ -1080,12 +938,14 @@ var OpenShellSandboxBackendImpl = class {
|
|
|
1080
938
|
const target = this.resolveRemoteTarget(remotePath);
|
|
1081
939
|
await this.runPinnedRemotePathMutation({
|
|
1082
940
|
args: [
|
|
1083
|
-
|
|
941
|
+
"remove",
|
|
1084
942
|
target.root,
|
|
1085
943
|
path.posix.dirname(target.relativePath) === "." ? "" : path.posix.dirname(target.relativePath),
|
|
1086
944
|
path.posix.basename(target.relativePath),
|
|
1087
|
-
params?.
|
|
945
|
+
params?.recursive ? "1" : "0",
|
|
946
|
+
"1"
|
|
1088
947
|
],
|
|
948
|
+
ignoreMissingParent: params?.ignoreMissing,
|
|
1089
949
|
signal: params?.signal
|
|
1090
950
|
});
|
|
1091
951
|
}
|
|
@@ -1100,7 +960,8 @@ var OpenShellSandboxBackendImpl = class {
|
|
|
1100
960
|
path.posix.basename(from.relativePath),
|
|
1101
961
|
to.root,
|
|
1102
962
|
path.posix.dirname(to.relativePath) === "." ? "" : path.posix.dirname(to.relativePath),
|
|
1103
|
-
path.posix.basename(to.relativePath)
|
|
963
|
+
path.posix.basename(to.relativePath),
|
|
964
|
+
"1"
|
|
1104
965
|
],
|
|
1105
966
|
signal
|
|
1106
967
|
});
|
|
@@ -1130,24 +991,11 @@ var OpenShellSandboxBackendImpl = class {
|
|
|
1130
991
|
await this.maybeSeedRemoteWorkspace();
|
|
1131
992
|
const target = this.resolveRemoteTarget(remotePath);
|
|
1132
993
|
const stats = await fsPromises.lstat(localPath).catch(() => null);
|
|
1133
|
-
if (!stats) {
|
|
1134
|
-
await this.
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
path.posix.basename(target.relativePath),
|
|
1139
|
-
"1"
|
|
1140
|
-
] });
|
|
1141
|
-
return;
|
|
1142
|
-
}
|
|
1143
|
-
if (stats.isSymbolicLink()) {
|
|
1144
|
-
await this.runPinnedRemotePathMutation({ args: [
|
|
1145
|
-
"remove",
|
|
1146
|
-
target.root,
|
|
1147
|
-
path.posix.dirname(target.relativePath) === "." ? "" : path.posix.dirname(target.relativePath),
|
|
1148
|
-
path.posix.basename(target.relativePath),
|
|
1149
|
-
"1"
|
|
1150
|
-
] });
|
|
994
|
+
if (!stats || stats.isSymbolicLink()) {
|
|
995
|
+
await this.removeRemotePath(remotePath, {
|
|
996
|
+
recursive: true,
|
|
997
|
+
ignoreMissing: true
|
|
998
|
+
});
|
|
1151
999
|
return;
|
|
1152
1000
|
}
|
|
1153
1001
|
if (stats.isDirectory()) {
|
|
@@ -1175,8 +1023,13 @@ var OpenShellSandboxBackendImpl = class {
|
|
|
1175
1023
|
}
|
|
1176
1024
|
async runPinnedRemotePathMutation(params) {
|
|
1177
1025
|
return await this.runRemoteShellScript({
|
|
1178
|
-
script:
|
|
1179
|
-
args:
|
|
1026
|
+
script: "python_script=\"$1\"; shift; python3 -c \"$python_script\" \"$@\"",
|
|
1027
|
+
args: [
|
|
1028
|
+
OPEN_SHELL_GUEST_MUTATION_PYTHON,
|
|
1029
|
+
GUEST_FILESYSTEM_PYTHON,
|
|
1030
|
+
params.ignoreMissingParent ? "1" : "0",
|
|
1031
|
+
...params.args
|
|
1032
|
+
],
|
|
1180
1033
|
signal: params.signal
|
|
1181
1034
|
});
|
|
1182
1035
|
}
|
|
@@ -1412,7 +1265,7 @@ var OpenShellSandboxBackendImpl = class {
|
|
|
1412
1265
|
}
|
|
1413
1266
|
const relativeSkillsPath = path.posix.relative(root.remote, remoteSkillsWorkspaceDir);
|
|
1414
1267
|
if (relativeSkillsPath === "" || !relativeSkillsPath.startsWith("../") && !path.posix.isAbsolute(relativeSkillsPath)) await removeDownloadedWorkspacePath(tmpDir, relativeSkillsPath.split("/").filter(Boolean));
|
|
1415
|
-
|
|
1268
|
+
await moveLocalShadowAside({
|
|
1416
1269
|
workspaceDir: root.local,
|
|
1417
1270
|
tmpDir,
|
|
1418
1271
|
relativeParts: MATERIALIZED_SKILLS_REMOTE_PARTS,
|
|
@@ -1567,14 +1420,7 @@ async function removeDownloadedWorkspacePath(tmpDir, parts) {
|
|
|
1567
1420
|
const next = path.join(cursor, part);
|
|
1568
1421
|
const stats = await fsPromises.lstat(next).catch(() => null);
|
|
1569
1422
|
if (!stats) return;
|
|
1570
|
-
if (index === parts.length - 1) {
|
|
1571
|
-
await fsPromises.rm(next, {
|
|
1572
|
-
recursive: true,
|
|
1573
|
-
force: true
|
|
1574
|
-
});
|
|
1575
|
-
return;
|
|
1576
|
-
}
|
|
1577
|
-
if (stats.isSymbolicLink() || !stats.isDirectory()) {
|
|
1423
|
+
if (index === parts.length - 1 || stats.isSymbolicLink() || !stats.isDirectory()) {
|
|
1578
1424
|
await fsPromises.rm(next, {
|
|
1579
1425
|
recursive: true,
|
|
1580
1426
|
force: true
|
|
@@ -2,6 +2,108 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.21.1 - 2026-09-27
|
|
6
|
+
|
|
7
|
+
### Fixes
|
|
8
|
+
|
|
9
|
+
- **Linux without `openat2`:** native opens now follow in-root relative symlinks exactly as `RESOLVE_BENEATH` does, rejecting absolute links, `..` escapes, procfs and `nosymfollow` links, and chains longer than 40. Denial and symlink policies report the same errors as the `openat2` path. Links inside sticky world-writable directories are refused. ([#717](https://github.com/openclaw/fs-safe/pull/717))
|
|
10
|
+
- **Deny policies on case-insensitive filesystems:** `denyMutations` now denies creating a not-yet-existing case or Unicode-normalization alias of a denied path (for example `case.txt` when `Case.txt` is denied on APFS, NTFS or casefold ext4). Where a filesystem's sensitivity cannot be proven, Unicode-equivalent names are denied conservatively. ([#720](https://github.com/openclaw/fs-safe/pull/720))
|
|
11
|
+
- **Directory listings:** `list()`, `entries()` and `walk()` reject names that are not valid UTF-8 with `invalid-path` instead of decoding them lossily, which could collide with a real `U+FFFD` name and report another entry's metadata. ([#715](https://github.com/openclaw/fs-safe/pull/715))
|
|
12
|
+
- **ZIP admission:** entries that differ only by case or Unicode normalization are rejected during preflight and member reads, matching extraction's collision policy. ([#716](https://github.com/openclaw/fs-safe/pull/716))
|
|
13
|
+
- **Atomic restore:** when a metadata call fails with `EIO` after the destination was truncated, restore-original writes the original bytes back through the retained, identity-verified descriptor instead of leaving the file empty. ([#718](https://github.com/openclaw/fs-safe/pull/718))
|
|
14
|
+
- **Lock and prune cleanup:** cleanup decisions compare exact bigint file identities. Legacy numeric receipts with IDs above 2^53 are refused instead of risking removal of the wrong file. ([#719](https://github.com/openclaw/fs-safe/pull/719))
|
|
15
|
+
- **Watch overflow:** subscriptions no longer publish spurious whole-scope overflow invalidations for excluded or unselected paths or for slow reconcile passes. macOS streams use OS-level exclusion paths and anchor-scoped streams, and Windows uses a 1 MiB change buffer on local volumes. Genuine OS event drops still invalidate. ([#724](https://github.com/openclaw/fs-safe/pull/724))
|
|
16
|
+
- **Mutation authority:** reject synchronous and asynchronous generator callback results before mutation, so lazy authority checks cannot be skipped. ([#711](https://github.com/openclaw/fs-safe/pull/711))
|
|
17
|
+
- **Synchronous atomic writes:** reject deferred `beforeRename` hooks before publication and clean up the owned stage. ([#728](https://github.com/openclaw/fs-safe/pull/728))
|
|
18
|
+
- **Copy publication callbacks:** reject deferred generator observers in `Root.copyIn()` while preserving the completed destination and source bytes. ([#726](https://github.com/openclaw/fs-safe/pull/726))
|
|
19
|
+
- **Native move diagnostics:** no-clobber `Root.move()` keeps the original native loader error as its cause, including missing libraries or incompatible glibc versions. ([#709](https://github.com/openclaw/fs-safe/pull/709))
|
|
20
|
+
|
|
21
|
+
### Features
|
|
22
|
+
|
|
23
|
+
- **Watch polling interval:** `pollIntervalMs` sets the scan interval whenever polling is the selected transport, including `mode: "auto"` falling back to polling, without changing the events reconcile interval. ([#723](https://github.com/openclaw/fs-safe/pull/723))
|
|
24
|
+
|
|
25
|
+
### Performance
|
|
26
|
+
|
|
27
|
+
- **Linux directory cloning:** reuse the admitted filesystem type when creating clone sources and cloning trees, avoiding a duplicate filesystem probe and allocation per operation. ([#732](https://github.com/openclaw/fs-safe/pull/732))
|
|
28
|
+
|
|
29
|
+
## 0.21.0 - 2026-09-26
|
|
30
|
+
|
|
31
|
+
### Highlights
|
|
32
|
+
|
|
33
|
+
- **Guarded filesystem observation:** `@openclaw/fs-safe/watch` observes literal entry and tree scopes beneath an admitted Root and delivers bounded advisory invalidations; guarded scans stay authoritative. One shared Rust event thread per process uses inotify, FSEvents and ReadDirectoryChangesW on Linux, macOS and Windows, sleeps while idle, and falls back to portable polling under `mode: "auto"`. Subscriptions become ready after one baseline scan, stay available under sustained writes, fence stale generations on `setScopes()`, join native work on `close()`, and support `persistent: false` so one-shot Node commands can exit. Thanks @vincentkoc. ([#690](https://github.com/openclaw/fs-safe/pull/690), [#695](https://github.com/openclaw/fs-safe/pull/695), [#697](https://github.com/openclaw/fs-safe/pull/697), [#703](https://github.com/openclaw/fs-safe/pull/703), [#707](https://github.com/openclaw/fs-safe/pull/707))
|
|
34
|
+
- **Linux user namespaces:** private temp workspaces now work beneath unmapped ancestors, including systemd user services with `PrivateUsers=true`, while UID/GID mappings and ancestor permissions are still rechecked. ([#693](https://github.com/openclaw/fs-safe/pull/693))
|
|
35
|
+
|
|
36
|
+
### Features
|
|
37
|
+
|
|
38
|
+
- **Revocable atomic writes:** atomic replacement can recheck caller authority before new effects and report retained destination identities for partial writes and publication, without treating receipts as permission to roll back. ([#694](https://github.com/openclaw/fs-safe/pull/694))
|
|
39
|
+
- **Root walking:** `symlinkPolicy: "include"` reports links without following their targets, preserving sorted traversal, entry budgets, and the existing skip/follow result types. Directory-to-symlink substitutions fail before descent. ([#692](https://github.com/openclaw/fs-safe/pull/692))
|
|
40
|
+
- **Windows file retirement:** the advanced surface can retire existing NTFS files through a retained handle, with exact producer identity, current authority, and separate disposition and settlement facts. There is no persistence guarantee. ([#706](https://github.com/openclaw/fs-safe/pull/706))
|
|
41
|
+
|
|
42
|
+
### Performance
|
|
43
|
+
|
|
44
|
+
- **Temporary filename sanitization:** skip duplicate normalization after reserved-device suffixing while preserving filename admission and fallback rules. ([#689](https://github.com/openclaw/fs-safe/pull/689))
|
|
45
|
+
|
|
46
|
+
### Compatibility
|
|
47
|
+
|
|
48
|
+
- Supplied temp workspace roots must be owned by the effective user and must not be group- or world-writable; use a private per-user root instead of shared `/tmp`. A one-time warning reports host ownership that cannot be verified. ([#693](https://github.com/openclaw/fs-safe/pull/693))
|
|
49
|
+
- On Windows, an events-mode watch holds one handle on each watched Root, so the Root's own ancestor directories cannot be renamed while it is open; renaming inside the Root is unaffected. Use `mode: "poll"` where that matters. ([#697](https://github.com/openclaw/fs-safe/pull/697))
|
|
50
|
+
- `watch()` requires an explicit `mode` (`"auto"`, `"events"` or `"poll"`); `"events"` fails readiness with `helper-unavailable` when no native backend is available, and Bun currently selects polling under `"auto"`. ([#690](https://github.com/openclaw/fs-safe/pull/690))
|
|
51
|
+
|
|
52
|
+
## 0.20.0 - 2026-09-25
|
|
53
|
+
|
|
54
|
+
### Highlights
|
|
55
|
+
|
|
56
|
+
- **Older Linux systems:** when `openat2` is absent (Linux before 5.6) or blocked by seccomp, native operations fall back to a guarded no-follow walk instead of failing with `ENOSYS`. Nested no-clobber moves keep working, and GNU x64/arm64 bindings now target glibc 2.28, so native operations load on RHEL 8-family systems. ([#686](https://github.com/openclaw/fs-safe/pull/686), [#685](https://github.com/openclaw/fs-safe/pull/685); fixes [#572](https://github.com/openclaw/fs-safe/issues/572), [#511](https://github.com/openclaw/fs-safe/issues/511), [#548](https://github.com/openclaw/fs-safe/issues/548))
|
|
57
|
+
- **Path hardening:** trash moves stay inside allowed roots, symlink-parent checks inspect raw segments before dotdot normalization, relative-escape checks recognize either Windows separator and nested escapes, and safe path segments reject Windows reserved device names. Thanks @SebTardif. ([#611](https://github.com/openclaw/fs-safe/pull/611), [#615](https://github.com/openclaw/fs-safe/pull/615), [#614](https://github.com/openclaw/fs-safe/pull/614), [#612](https://github.com/openclaw/fs-safe/pull/612))
|
|
58
|
+
- **ZIP admission per operation:** portable ZIP reads and extraction bind entry selection to the admitted archive, name, and decoder object, and verify payloads against the admitted CRC and size. Substituted, renamed, or replaced decoder entries are rejected. ([#660](https://github.com/openclaw/fs-safe/pull/660))
|
|
59
|
+
|
|
60
|
+
### Features
|
|
61
|
+
|
|
62
|
+
- **Retained symlink publication:** `retainSymlinkInDirectory()` on the advanced surface holds an explicitly identified POSIX symlink through exact-slot no-replace publication and explicit recovery, preserving observed foreign replacements and uncertain outcomes. ([#682](https://github.com/openclaw/fs-safe/pull/682))
|
|
63
|
+
- **Batched Windows ACL facts:** `readOwnerAndDaclBatch()` inspects ordered paths in one isolated native worker or one PowerShell process, with a configurable whole-batch timeout and bounded output. Results are bounded during collection; oversized batches reject with `too-large` before later paths are queried. ([#669](https://github.com/openclaw/fs-safe/pull/669), [#680](https://github.com/openclaw/fs-safe/pull/680))
|
|
64
|
+
- **Atomic text options:** `writeTextAtomic()` forwards the existing `beforeRename` hook and `tempPrefix` option to atomic replacement, keeping its validation and identity checks. ([#580](https://github.com/openclaw/fs-safe/pull/580))
|
|
65
|
+
|
|
66
|
+
### Fixes
|
|
67
|
+
|
|
68
|
+
- **Lock exit cleanup:** leave raw sidecars in place when Windows reports an unknown device or inode, while keeping token-owned cleanup working when the descriptor and path identities legitimately differ (for example on VirtioFS). Thanks @SebTardif. ([#617](https://github.com/openclaw/fs-safe/pull/617))
|
|
69
|
+
- **Retained symlink errors:** preserve uncertain publication outcomes and cached cleanup/recovery failures when inspecting thrown error metadata fails, retaining the original cause without retrying mutations, callbacks, or descriptor closes. ([#684](https://github.com/openclaw/fs-safe/pull/684))
|
|
70
|
+
- **Create collision cleanup:** atomic and streamed creates remove their private stage when the JavaScript fallback observes a competing destination before publication; failures after a link attempt retain their existing recovery evidence. ([#683](https://github.com/openclaw/fs-safe/pull/683))
|
|
71
|
+
|
|
72
|
+
### Compatibility
|
|
73
|
+
|
|
74
|
+
- Safe path segments now reject Windows reserved device names (`CON`, `NUL`, `COM1`, `CON.json`, …) on every platform, because segments are portable identifiers. Temporary filename sanitization still suffixes them (`CON.txt` becomes `CON_.txt`). ([#612](https://github.com/openclaw/fs-safe/pull/612))
|
|
75
|
+
- `assertNoSymlinkParents()` and guarded appends with `rejectSymlinkParents` reject raw spellings whose dotdot segments would cancel a symlink or re-enter the root after leaving it. ([#615](https://github.com/openclaw/fs-safe/pull/615))
|
|
76
|
+
- Without `openat2`, beneath opens report `best-effort` containment instead of `kernel-atomic`, anonymous `O_TMPFILE` opens report `ENOTSUP`, and bounded tree cleanup fails closed with `helper-unavailable` because it requires `RESOLVE_NO_XDEV`. ([#686](https://github.com/openclaw/fs-safe/pull/686))
|
|
77
|
+
- The Linux GNU binding floor is glibc 2.28 for both x64 and arm64 (arm64 was previously built against 2.17), matching Node 22's own Linux baseline. ([#685](https://github.com/openclaw/fs-safe/pull/685))
|
|
78
|
+
|
|
79
|
+
## 0.19.0 - 2026-09-24
|
|
80
|
+
|
|
81
|
+
### Highlights
|
|
82
|
+
|
|
83
|
+
- **Strict secret-file durability:** `createSecretFileAtomic()` accepts `durable: "file"` to require every file flush to succeed, including on `EPERM`; parent-directory synchronization remains best effort. ([#644](https://github.com/openclaw/fs-safe/pull/644))
|
|
84
|
+
- **Literal `~` names:** keep FileStore keys, absolute Root reads, discovered walk entries, and ZIP/TAR entries literal instead of treating them as home-directory shorthand. Reads, writes, removal, pruning, extraction, and durable publication select the intended entry. ([#633](https://github.com/openclaw/fs-safe/pull/633))
|
|
85
|
+
- **Native Unix descriptor safety:** reject negative descriptors in low-level root, query, hash, copy, clone, staging, and cleanup calls without Rust panics or working-directory operations. Modern macOS beneath opens report `EBADF` for negative roots instead of `EIO`. ([#640](https://github.com/openclaw/fs-safe/pull/640), [#643](https://github.com/openclaw/fs-safe/pull/643), [#646](https://github.com/openclaw/fs-safe/pull/646))
|
|
86
|
+
|
|
87
|
+
### Fixes
|
|
88
|
+
|
|
89
|
+
- **Guest directory permissions:** cross-device moves preserve mode `000`, including nested directories, without changing other modes' umask behavior. Restore top-level permissions through the retained directory descriptor and preserve published entries on failure. Thanks @SebTardif. ([#616](https://github.com/openclaw/fs-safe/pull/616))
|
|
90
|
+
- **ReFS clone cleanup:** remove ordinary partial output when Windows rejects the ignore-readonly deletion flag. Keep readonly attributes intact and include cleanup failures in the original clone error; readonly files or other processes' open handles can still leave output behind. ([#648](https://github.com/openclaw/fs-safe/pull/648))
|
|
91
|
+
|
|
92
|
+
### Compatibility and documentation
|
|
93
|
+
|
|
94
|
+
- Secret creation's default and boolean durability options retain their behavior, and `writeSecretFileAtomic()` remains boolean-only. Strict file synchronization preserves existing publication and cleanup semantics: a failure after publication can leave a complete file present. ([#644](https://github.com/openclaw/fs-safe/pull/644))
|
|
95
|
+
- `Root.walk("~")` and `Root.walk("~/dir")` expand home shorthand when iteration starts and return admitted canonical paths relative to the Root. Use `./~/dir` for a literal tilde directory, and prefix returned entry paths with `./` when passing them to another Root method. Relative Root `~/name` inputs still expand home; FileStore keys do not. ([#633](https://github.com/openclaw/fs-safe/pull/633))
|
|
96
|
+
- Guest moves still require OS permission to read mode-000 source directories; they do not widen source permissions. If permission restoration fails after publication, the source and published copy remain for caller reconciliation. ([#616](https://github.com/openclaw/fs-safe/pull/616))
|
|
97
|
+
- Clarify that `Root.append()` and `openWritable()` creation modes remain subject to the process umask and do not chmod existing files. This documents existing behavior. ([#636](https://github.com/openclaw/fs-safe/pull/636))
|
|
98
|
+
|
|
99
|
+
## 0.18.2 - 2026-09-22
|
|
100
|
+
|
|
101
|
+
### Fixes
|
|
102
|
+
|
|
103
|
+
- **Lock errors:** preserve Root stale-removal rejections and `null` or `undefined` retry failures instead of masking them with timeouts or `TypeError`s. ([#624](https://github.com/openclaw/fs-safe/pull/624))
|
|
104
|
+
- **Secret-file reads:** report caught `null` or `undefined` inspection and read failures as structured errors instead of internal `TypeError`s. ([#623](https://github.com/openclaw/fs-safe/pull/623))
|
|
105
|
+
- **Secret-file creation:** preserve `null` or `undefined` thrown by parameter getters instead of masking them with an internal `TypeError`. ([#625](https://github.com/openclaw/fs-safe/pull/625))
|
|
106
|
+
|
|
5
107
|
## 0.18.1 - 2026-09-22
|
|
6
108
|
|
|
7
109
|
### Fixes
|