@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 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 { removePathWithinRoot, root } from "openclaw/plugin-sdk/file-access-runtime";
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: ${formatError(err)}`, { cause: err });
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}: ${formatError(err)}`, { cause: err instanceof Error ? err : void 0 });
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
- const relative = path.relative(workspace, candidate);
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 (isNodeError(err) && err.code === "ENOENT") throw new Error(`MXC sandbox workdir ${workdir} does not exist.`, { cause: err });
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 (isNodeError(err) && err.code === "ENOENT") throw new Error(`MXC ${label} ${value} does not exist.`, { cause: err });
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 (!isNodeError(err) || err.code !== "ENOENT") throw err;
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
- const spawned = await spawnSandboxFromConfig(config, options ?? {});
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.7.2-beta.7",
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.7.2-beta.7"
24
+ "pluginApi": ">=2026.8.1-beta.3"
25
25
  },
26
26
  "build": {
27
- "openclawVersion": "2026.7.2-beta.7",
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.7.2-beta.7"
50
+ "openclaw": ">=2026.8.1-beta.3"
51
51
  },
52
52
  "peerDependenciesMeta": {
53
53
  "openclaw": {