@openclaw/mxc-sandbox 2026.7.2-beta.7 → 2026.8.1-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +24 -16
- package/dist/mxc-spawn-launcher.mjs +30 -17
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -10,10 +10,11 @@ import { createWritableRenameTargetResolver, registerSandboxBackend, resolvePref
|
|
|
10
10
|
import * as fs$1 from "node:fs";
|
|
11
11
|
import fs, { lstatSync, mkdtempSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
12
12
|
import { createHash, randomBytes } from "node:crypto";
|
|
13
|
+
import { isPathInside, removePathWithinRoot, root } from "openclaw/plugin-sdk/file-access-runtime";
|
|
13
14
|
import { runCommandBuffered } from "openclaw/plugin-sdk/process-runtime";
|
|
14
|
-
import {
|
|
15
|
-
import { FsSafeError, isPathInside } from "openclaw/plugin-sdk/security-runtime";
|
|
15
|
+
import { FsSafeError } from "openclaw/plugin-sdk/security-runtime";
|
|
16
16
|
import { fileURLToPath } from "node:url";
|
|
17
|
+
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
17
18
|
import { execFileSync } from "node:child_process";
|
|
18
19
|
//#region extensions/mxc/src/config.ts
|
|
19
20
|
const MXC_CONTAINMENTS = ["process", "processcontainer"];
|
|
@@ -183,10 +184,10 @@ function createMxcFsBridge(params) {
|
|
|
183
184
|
var MxcFsBridge = class {
|
|
184
185
|
constructor(sandbox) {
|
|
185
186
|
this.sandbox = sandbox;
|
|
186
|
-
this.defaultContainerRoot = path.resolve(this.sandbox.containerWorkdir);
|
|
187
|
-
this.protectedSkillMounts = resolveMxcProtectedSkillMounts$1(this.sandbox);
|
|
188
|
-
this.workspaceMounts = resolveWorkspaceMounts(this.sandbox);
|
|
189
187
|
this.resolveRenameTargets = createWritableRenameTargetResolver((target) => this.resolveTarget(target), (target, action) => this.ensureWritable(target, action));
|
|
188
|
+
this.defaultContainerRoot = path.resolve(sandbox.containerWorkdir);
|
|
189
|
+
this.protectedSkillMounts = resolveMxcProtectedSkillMounts$1(sandbox);
|
|
190
|
+
this.workspaceMounts = resolveWorkspaceMounts(sandbox);
|
|
190
191
|
}
|
|
191
192
|
resolvePath(params) {
|
|
192
193
|
const target = this.resolveTarget(params);
|
|
@@ -929,7 +930,7 @@ function assertConfiguredPathExists(pathValue, source) {
|
|
|
929
930
|
} catch (err) {
|
|
930
931
|
if (isNodeError$1(err)) {
|
|
931
932
|
if (err.code === "ENOENT") throw new Error(`Sandbox policy path ${pathValue} configured by ${source} does not exist on the host. Create the path or update the policy file.`, { cause: err });
|
|
932
|
-
throw new Error(`Sandbox policy path ${pathValue} configured by ${source} is not accessible on the host: ${
|
|
933
|
+
throw new Error(`Sandbox policy path ${pathValue} configured by ${source} is not accessible on the host: ${formatErrorMessage(err)}`, { cause: err });
|
|
933
934
|
}
|
|
934
935
|
throw err;
|
|
935
936
|
}
|
|
@@ -942,7 +943,7 @@ function assertPositiveFiniteNumber(value, label) {
|
|
|
942
943
|
}
|
|
943
944
|
function policyFileError(policyPath, err) {
|
|
944
945
|
if (isNodeError$1(err) && err.code === "ENOENT") return new Error(`Configured sandbox policy file ${policyPath} does not exist. Remove it from mxcPolicyPaths or create the file.`, { cause: err });
|
|
945
|
-
return new Error(`Failed to load sandbox policy file at ${policyPath}: ${
|
|
946
|
+
return new Error(`Failed to load sandbox policy file at ${policyPath}: ${formatErrorMessage(err)}`, { cause: err instanceof Error ? err : void 0 });
|
|
946
947
|
}
|
|
947
948
|
function formatSandboxPolicyIssue(sourceLabel, issue) {
|
|
948
949
|
if (!issue) return `Sandbox policy at ${sourceLabel} is invalid.`;
|
|
@@ -965,10 +966,6 @@ function formatIssuePath(pathSegments) {
|
|
|
965
966
|
}
|
|
966
967
|
return label;
|
|
967
968
|
}
|
|
968
|
-
function formatError(err) {
|
|
969
|
-
if (err instanceof Error && err.message) return err.message;
|
|
970
|
-
return String(err);
|
|
971
|
-
}
|
|
972
969
|
function isNodeError$1(err) {
|
|
973
970
|
return err instanceof Error && "code" in err;
|
|
974
971
|
}
|
|
@@ -1017,8 +1014,7 @@ function createSandboxTempDir(hostEnv) {
|
|
|
1017
1014
|
function assertWorkdirInsideWorkspace(workspaceDir, workdir) {
|
|
1018
1015
|
const workspace = realpathForExistingPath(workspaceDir, "sandbox workspace");
|
|
1019
1016
|
const candidate = realpathForPotentialPath(workdir);
|
|
1020
|
-
|
|
1021
|
-
if (relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative)) return candidate;
|
|
1017
|
+
if (isPathInside(workspace, candidate)) return candidate;
|
|
1022
1018
|
throw new Error(`MXC sandbox workdir ${workdir} is outside the sandbox workspace ${workspaceDir}. Use a workdir inside the sandbox workspace.`);
|
|
1023
1019
|
}
|
|
1024
1020
|
function resolveWorkdirInsideWorkspace(workspaceDir, workdir) {
|
|
@@ -1026,7 +1022,7 @@ function resolveWorkdirInsideWorkspace(workspaceDir, workdir) {
|
|
|
1026
1022
|
try {
|
|
1027
1023
|
if (statSync(candidate).isDirectory()) return candidate;
|
|
1028
1024
|
} catch (err) {
|
|
1029
|
-
if (
|
|
1025
|
+
if (isMissingPathError(err)) throw new Error(`MXC sandbox workdir ${workdir} does not exist.`, { cause: err });
|
|
1030
1026
|
throw err;
|
|
1031
1027
|
}
|
|
1032
1028
|
throw new Error(`MXC sandbox workdir ${workdir} is not a directory.`);
|
|
@@ -1035,7 +1031,7 @@ function realpathForExistingPath(value, label) {
|
|
|
1035
1031
|
try {
|
|
1036
1032
|
return realpathSync(path.resolve(value));
|
|
1037
1033
|
} catch (err) {
|
|
1038
|
-
if (
|
|
1034
|
+
if (isMissingPathError(err)) throw new Error(`MXC ${label} ${value} does not exist.`, { cause: err });
|
|
1039
1035
|
throw err;
|
|
1040
1036
|
}
|
|
1041
1037
|
}
|
|
@@ -1044,7 +1040,7 @@ function realpathForPotentialPath(value) {
|
|
|
1044
1040
|
try {
|
|
1045
1041
|
return realpathSync(resolved);
|
|
1046
1042
|
} catch (err) {
|
|
1047
|
-
if (!
|
|
1043
|
+
if (!isMissingPathError(err)) throw err;
|
|
1048
1044
|
const parent = path.dirname(resolved);
|
|
1049
1045
|
if (parent === resolved) throw new Error(`MXC sandbox workdir ${value} does not exist.`, { cause: err });
|
|
1050
1046
|
return path.join(realpathForPotentialPath(parent), path.basename(resolved));
|
|
@@ -1053,6 +1049,9 @@ function realpathForPotentialPath(value) {
|
|
|
1053
1049
|
function isNodeError(err) {
|
|
1054
1050
|
return err instanceof Error && "code" in err;
|
|
1055
1051
|
}
|
|
1052
|
+
function isMissingPathError(err) {
|
|
1053
|
+
return isNodeError(err) && (err.code === "ENOENT" || err.code === "ENOTDIR");
|
|
1054
|
+
}
|
|
1056
1055
|
function buildMxcLauncherOptions(config, usePty) {
|
|
1057
1056
|
const options = {
|
|
1058
1057
|
debug: config.debug ?? false,
|
|
@@ -1087,6 +1086,15 @@ function createMxcSandboxBackendHandle(params) {
|
|
|
1087
1086
|
runtimeId: params.runtimeId,
|
|
1088
1087
|
runtimeLabel: params.runtimeId,
|
|
1089
1088
|
workdir: params.workdir,
|
|
1089
|
+
workdirValidation: "backend",
|
|
1090
|
+
async validateWorkdir(workdir) {
|
|
1091
|
+
try {
|
|
1092
|
+
return resolveWorkdirInsideWorkspace(params.workdir, workdir);
|
|
1093
|
+
} catch (err) {
|
|
1094
|
+
if (err instanceof Error && err.message.startsWith("MXC sandbox workdir")) return null;
|
|
1095
|
+
throw err;
|
|
1096
|
+
}
|
|
1097
|
+
},
|
|
1090
1098
|
capabilities: {},
|
|
1091
1099
|
async buildExecSpec({ command, workdir, env, usePty }) {
|
|
1092
1100
|
const effectiveWorkdir = resolveWorkdirInsideWorkspace(params.workdir, workdir ?? params.workdir);
|
|
@@ -35,9 +35,9 @@ export function forwardSignals(spawned, options = {}) {
|
|
|
35
35
|
if (exitTimer) {
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
|
-
const setTimeoutFn = options.setTimeout ?? setTimeout;
|
|
38
|
+
const setTimeoutFn = options.setTimeout?.bind(undefined) ?? setTimeout;
|
|
39
39
|
exitTimer = setTimeoutFn(() => {
|
|
40
|
-
const exit = options.exit ?? process.exit;
|
|
40
|
+
const exit = options.exit?.bind(undefined) ?? ((code) => process.exit(code));
|
|
41
41
|
exit(signalExitCode(signal));
|
|
42
42
|
}, exitGraceMs);
|
|
43
43
|
exitTimer?.unref?.();
|
|
@@ -86,11 +86,37 @@ function bridgeChildProcess(child) {
|
|
|
86
86
|
|
|
87
87
|
export function exitOnChildProcessClose(child, options = {}) {
|
|
88
88
|
child.on("close", (exitCode, signal) => {
|
|
89
|
-
const exit = options.exit ?? process.exit;
|
|
89
|
+
const exit = options.exit?.bind(undefined) ?? ((code) => process.exit(code));
|
|
90
90
|
exit(typeof exitCode === "number" ? exitCode : signalExitCode(signal));
|
|
91
91
|
});
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
function attachPtyProcess(spawned) {
|
|
95
|
+
bridgeStdio(spawned);
|
|
96
|
+
forwardSignals(spawned);
|
|
97
|
+
spawned.onExit(({ exitCode, signal }) => {
|
|
98
|
+
process.exit(typeof exitCode === "number" ? exitCode : signalExitCode(signal));
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function attachChildProcess(spawned) {
|
|
103
|
+
bridgeChildProcess(spawned);
|
|
104
|
+
forwardSignals(spawned);
|
|
105
|
+
exitOnChildProcessClose(spawned);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function launchSandbox(spawnSandboxFromConfig, config, options, bridges = {}) {
|
|
109
|
+
// Normalize sync and Promise-returning SDK implementations before selecting an I/O bridge.
|
|
110
|
+
const spawned = await spawnSandboxFromConfig(config, options ?? {});
|
|
111
|
+
|
|
112
|
+
if (typeof spawned.onData === "function") {
|
|
113
|
+
(bridges.pty ?? attachPtyProcess)(spawned);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
(bridges.child ?? attachChildProcess)(spawned);
|
|
118
|
+
}
|
|
119
|
+
|
|
94
120
|
const SIGNAL_NUMBERS = new Map([
|
|
95
121
|
["SIGHUP", 1],
|
|
96
122
|
["SIGINT", 2],
|
|
@@ -123,20 +149,7 @@ export async function main() {
|
|
|
123
149
|
try {
|
|
124
150
|
const { config, options } = decodePayload(process.argv.slice(2));
|
|
125
151
|
const { spawnSandboxFromConfig } = await import("@microsoft/mxc-sdk");
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (typeof spawned.onData === "function") {
|
|
129
|
-
bridgeStdio(spawned);
|
|
130
|
-
forwardSignals(spawned);
|
|
131
|
-
spawned.onExit(({ exitCode, signal }) => {
|
|
132
|
-
process.exit(typeof exitCode === "number" ? exitCode : signalExitCode(signal));
|
|
133
|
-
});
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
bridgeChildProcess(spawned);
|
|
138
|
-
forwardSignals(spawned);
|
|
139
|
-
exitOnChildProcessClose(spawned);
|
|
152
|
+
await launchSandbox(spawnSandboxFromConfig, config, options);
|
|
140
153
|
} catch (error) {
|
|
141
154
|
process.stderr.write(`${formatErrorStack(error)}\n`);
|
|
142
155
|
process.exit(127);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/mxc-sandbox",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.8.1-beta.3",
|
|
4
4
|
"description": "OpenClaw MXC sandbox execution plugin for MXC-capable hosts",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
"minHostVersion": ">=2026.6.11"
|
|
22
22
|
},
|
|
23
23
|
"compat": {
|
|
24
|
-
"pluginApi": ">=2026.
|
|
24
|
+
"pluginApi": ">=2026.8.1-beta.3"
|
|
25
25
|
},
|
|
26
26
|
"build": {
|
|
27
|
-
"openclawVersion": "2026.
|
|
27
|
+
"openclawVersion": "2026.8.1-beta.3",
|
|
28
28
|
"bundledDist": false,
|
|
29
29
|
"staticAssets": [
|
|
30
30
|
{
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"README.md"
|
|
48
48
|
],
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"openclaw": ">=2026.
|
|
50
|
+
"openclaw": ">=2026.8.1-beta.3"
|
|
51
51
|
},
|
|
52
52
|
"peerDependenciesMeta": {
|
|
53
53
|
"openclaw": {
|