@openclaw/mxc-sandbox 2026.9.1-beta.1 → 2026.9.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.
Files changed (3) hide show
  1. package/README.md +37 -103
  2. package/dist/index.js +21 -23
  3. package/package.json +4 -4
package/README.md CHANGED
@@ -101,44 +101,36 @@ help stay in sync with plugin runtime validation.
101
101
  - Windows filesystem-deny and host-list network policy knobs are not exposed by
102
102
  this plugin until MXC can enforce them on ProcessContainer.
103
103
 
104
- ## Test setup with `openclaw config`
104
+ ## Test setup
105
105
 
106
- This patch creates a default `main` agent, then adds a dedicated `mxc-test`
107
- agent so MXC testing does not change the default agent. It uses
106
+ Use an already configured OpenClaw installation with MXC installed and enabled
107
+ on a supported Windows host. These commands create a uniquely named test agent
108
+ and a new temporary workspace, leaving existing agents and workspaces alone.
108
109
  [`openclaw config patch --stdin`](https://docs.openclaw.ai/cli/config#config-patch)
109
- so setup is one validated config write instead of several path-based
110
- `config set` commands.
110
+ applies sandbox settings only to that agent. Installation-wide MXC settings
111
+ and policy files remain unchanged; review them before testing because they
112
+ apply to the test agent too.
111
113
 
112
- If you already have `agents.list` entries, copy them into the patch before
113
- `mxc-test` instead of replacing the list.
114
+ Run setup, testing, and cleanup in the same PowerShell session. Finish cleanup
115
+ before running setup again. Stop if any command fails.
114
116
 
115
117
  ```powershell
116
- $mxcPolicyPath = Join-Path $env:TEMP "openclaw-mxc-policy.json"
117
- @'
118
- {
119
- "filesystem": {
120
- "restrictToProjectDir": true,
121
- "additionalReadonlyPaths": [],
122
- "additionalReadwritePaths": []
123
- },
124
- "process": {
125
- "timeoutSeconds": 120
126
- }
127
- }
128
- '@ | Set-Content -Path $mxcPolicyPath -Encoding utf8
118
+ $mxcAgentCreated = $false
119
+ $mxcAgent = "mxc-test-" + [guid]::NewGuid().ToString("N")
120
+ $mxcWorkspace = Join-Path ([System.IO.Path]::GetTempPath()) $mxcAgent
121
+ New-Item -ItemType Directory -Path $mxcWorkspace -ErrorAction Stop | Out-Null
122
+
123
+ openclaw agents add $mxcAgent `
124
+ --workspace $mxcWorkspace `
125
+ --non-interactive
126
+ if ($LASTEXITCODE -ne 0) { throw "Agent creation failed; stop without patching or deleting an existing agent." }
127
+ $mxcAgentCreated = $true
129
128
 
130
- $mxcPolicyPathLiteral = ConvertTo-Json $mxcPolicyPath -Compress
131
129
  $mxcConfigPatch = @"
132
130
  {
133
131
  agents: {
134
- list: [
135
- {
136
- id: "main",
137
- workspace: "~/.openclaw/workspace",
138
- },
139
- {
140
- id: "mxc-test",
141
- workspace: "~/.openclaw/workspace-mxc-test",
132
+ entries: {
133
+ "$mxcAgent": {
142
134
  sandbox: {
143
135
  mode: "all",
144
136
  backend: "mxc",
@@ -146,62 +138,15 @@ $mxcConfigPatch = @"
146
138
  workspaceAccess: "none",
147
139
  },
148
140
  },
149
- ],
150
- },
151
- plugins: {
152
- entries: {
153
- mxc: {
154
- enabled: true,
155
- config: {
156
- containment: "process",
157
- network: "none",
158
- mxcPolicyPaths: [$mxcPolicyPathLiteral],
159
- },
160
- },
161
141
  },
162
142
  },
163
143
  }
164
144
  "@
165
145
 
166
146
  $mxcConfigPatch | openclaw config patch --stdin --dry-run
147
+ if ($LASTEXITCODE -ne 0) { throw "Sandbox validation failed; use Cleanup to remove the new test agent." }
167
148
  $mxcConfigPatch | openclaw config patch --stdin
168
- ```
169
-
170
- Resulting config shape:
171
-
172
- ```jsonc
173
- {
174
- "agents": {
175
- "list": [
176
- {
177
- "id": "main",
178
- "workspace": "~/.openclaw/workspace",
179
- },
180
- {
181
- "id": "mxc-test",
182
- "workspace": "~/.openclaw/workspace-mxc-test",
183
- "sandbox": {
184
- "mode": "all",
185
- "backend": "mxc",
186
- "scope": "agent",
187
- "workspaceAccess": "none",
188
- },
189
- },
190
- ],
191
- },
192
- "plugins": {
193
- "entries": {
194
- "mxc": {
195
- "enabled": true,
196
- "config": {
197
- "containment": "process",
198
- "network": "none",
199
- "mxcPolicyPaths": ["C:\\Users\\you\\AppData\\Local\\Temp\\openclaw-mxc-policy.json"],
200
- },
201
- },
202
- },
203
- },
204
- }
149
+ if ($LASTEXITCODE -ne 0) { throw "Sandbox setup failed; use Cleanup to remove the new test agent." }
205
150
  ```
206
151
 
207
152
  ## Sandbox policy files
@@ -283,44 +228,33 @@ ProcessContainer cannot safely enforce the nested read-only grant.
283
228
  Run the TUI as that agent:
284
229
 
285
230
  ```powershell
286
- openclaw tui --session agent:mxc-test:main
231
+ openclaw tui --session "agent:${mxcAgent}:main"
287
232
  ```
288
233
 
289
234
  For local embedded testing without a Gateway:
290
235
 
291
236
  ```powershell
292
- openclaw tui --local --session agent:mxc-test:main
237
+ openclaw tui --local --session "agent:${mxcAgent}:main"
293
238
  ```
294
239
 
295
240
  ## Cleanup
296
241
 
297
- If you used the exact sample above, remove the test agent and MXC plugin
298
- configuration by patching the config back to the default-only shape:
242
+ In the same PowerShell session, remove only the agent created by setup:
299
243
 
300
244
  ```powershell
301
- $mxcCleanupPatch = @'
302
- {
303
- agents: {
304
- list: [
305
- {
306
- id: "main",
307
- workspace: "~/.openclaw/workspace",
308
- },
309
- ],
310
- },
311
- plugins: {
312
- entries: {
313
- mxc: null,
314
- },
315
- },
245
+ if (-not $mxcAgentCreated) {
246
+ throw "No successfully created test agent in this session; do not delete an existing agent."
316
247
  }
317
- '@
318
-
319
- $mxcCleanupPatch | openclaw config patch --stdin --dry-run
320
- $mxcCleanupPatch | openclaw config patch --stdin
321
- Remove-Item -Path $mxcPolicyPath -ErrorAction SilentlyContinue
248
+ openclaw agents delete $mxcAgent --force
249
+ if ($LASTEXITCODE -ne 0) { throw "Agent cleanup failed; inspect the error before retrying." }
250
+ $mxcAgentCreated = $false
322
251
  ```
323
252
 
253
+ The delete command removes the test agent's configuration and attempts to move
254
+ its workspace and state to Trash. Check its output for any manual-cleanup
255
+ warning. Do not remove the MXC plugin entry or shared policy files: other
256
+ agents may still use them.
257
+
324
258
  ## Host readiness
325
259
 
326
260
  IsoEnvBroker must be available on the host OS. The plugin checks this before
@@ -342,7 +276,7 @@ wxc-host-prep prepare-system-drive
342
276
  pnpm test:extension mxc
343
277
  ```
344
278
 
345
- `pnpm test extensions/mxc` is equivalent and also works.
279
+ `pnpm test extensions/mxc` is also supported for the bundled extension test lane.
346
280
 
347
281
  For policy-only edits, the focused coverage is in:
348
282
 
package/dist/index.js CHANGED
@@ -131,6 +131,12 @@ function resolveMxcBinaryPath(configOverride) {
131
131
  return found;
132
132
  }
133
133
  //#endregion
134
+ //#region extensions/mxc/src/path-comparison.ts
135
+ function normalizeMxcPathForComparison(value) {
136
+ const resolved = path.resolve(value);
137
+ return process.platform === "win32" ? resolved.toLowerCase() : resolved;
138
+ }
139
+ //#endregion
134
140
  //#region extensions/mxc/src/workspace-skill-mounts.ts
135
141
  const MATERIALIZED_SANDBOX_SKILLS_WORKSPACE_PARTS = [".openclaw", "sandbox-skills"];
136
142
  function containerJoin(root, ...parts) {
@@ -299,7 +305,7 @@ function resolveWorkspaceMounts(sandbox) {
299
305
  containerRoot,
300
306
  writable: false
301
307
  }];
302
- if (sandbox.workspaceAccess === "ro" && normalizePathForComparison$1(agentWorkspaceDir) !== normalizePathForComparison$1(workspaceDir)) mounts.push({
308
+ if (sandbox.workspaceAccess === "ro" && normalizeMxcPathForComparison(agentWorkspaceDir) !== normalizeMxcPathForComparison(workspaceDir)) mounts.push({
303
309
  hostRoot: agentWorkspaceDir,
304
310
  containerRoot: agentWorkspaceDir,
305
311
  writable: false
@@ -324,7 +330,7 @@ function normalizeMxcProtectedSkillMount(mount) {
324
330
  function dedupeAndSortMounts(mounts) {
325
331
  const deduped = /* @__PURE__ */ new Map();
326
332
  for (const mount of mounts) {
327
- const key = `${normalizePathForComparison$1(mount.hostRoot)}::${normalizePathForComparison$1(mount.containerRoot)}`;
333
+ const key = `${normalizeMxcPathForComparison(mount.hostRoot)}::${normalizeMxcPathForComparison(mount.containerRoot)}`;
328
334
  if (!deduped.has(key)) deduped.set(key, mount);
329
335
  }
330
336
  return [...deduped.values()].toSorted((left, right) => {
@@ -338,11 +344,7 @@ function resolveRelativeParentPath(relativePath) {
338
344
  return parent === "." || parent === "" ? null : parent;
339
345
  }
340
346
  function isSameMountRoot(first, second) {
341
- return normalizePathForComparison$1(first) === normalizePathForComparison$1(second);
342
- }
343
- function normalizePathForComparison$1(value) {
344
- const resolved = path.resolve(value);
345
- return process.platform === "win32" ? resolved.toLowerCase() : resolved;
347
+ return normalizeMxcPathForComparison(first) === normalizeMxcPathForComparison(second);
346
348
  }
347
349
  //#endregion
348
350
  //#region extensions/mxc/src/sandbox-baseline.ts
@@ -646,7 +648,7 @@ function resolveWorkspaceReadwritePathSpecs(workspace) {
646
648
  function resolveWorkspaceReadonlyPathSpecs(workspace) {
647
649
  if (workspace.workspaceAccess === "rw") return [];
648
650
  const readonlyPathSpecs = [requiredFilesystemPath(workspace.workspaceDir)];
649
- if (workspace.workspaceAccess === "ro" && normalizePathForComparison(workspace.agentWorkspaceDir) !== normalizePathForComparison(workspace.workspaceDir)) readonlyPathSpecs.push(requiredFilesystemPath(workspace.agentWorkspaceDir));
651
+ if (workspace.workspaceAccess === "ro" && normalizeMxcPathForComparison(workspace.agentWorkspaceDir) !== normalizeMxcPathForComparison(workspace.workspaceDir)) readonlyPathSpecs.push(requiredFilesystemPath(workspace.agentWorkspaceDir));
650
652
  return readonlyPathSpecs;
651
653
  }
652
654
  function resolveBaselineReadonlyPathSpecs(baseline, context) {
@@ -656,9 +658,9 @@ function resolveMxcProtectedSkillPolicyPaths(context) {
656
658
  const deduped = /* @__PURE__ */ new Map();
657
659
  for (const mount of resolveMxcProtectedSkillMounts(context)) {
658
660
  const hostPath = path.resolve(mount.hostPath);
659
- deduped.set(normalizePathForComparison(hostPath), hostPath);
661
+ deduped.set(normalizeMxcPathForComparison(hostPath), hostPath);
660
662
  const containerPath = path.resolve(mount.containerPath);
661
- deduped.set(normalizePathForComparison(containerPath), containerPath);
663
+ deduped.set(normalizeMxcPathForComparison(containerPath), containerPath);
662
664
  }
663
665
  return [...deduped.values()];
664
666
  }
@@ -676,7 +678,7 @@ function resolveMxcProtectedSkillMounts(context) {
676
678
  function resolveExistingFilesystemPaths(pathSpecs, accessLabel) {
677
679
  const deduped = /* @__PURE__ */ new Map();
678
680
  for (const pathSpec of pathSpecs) {
679
- const key = normalizePathForComparison(pathSpec.path);
681
+ const key = normalizeMxcPathForComparison(pathSpec.path);
680
682
  const existing = deduped.get(key);
681
683
  if (existing) {
682
684
  existing.required ||= pathSpec.required;
@@ -740,14 +742,10 @@ function assertNoMxcReadwriteReadonlyOverlap(params) {
740
742
  for (const readwritePath of params.readwritePaths) for (const readonlyPath of params.readonlyPaths) if (pathsOverlap(readwritePath, readonlyPath)) throw new Error(`MXC readwrite path ${readwritePath} overlaps read-only path ${readonlyPath}. Windows MXC cannot safely enforce nested read-only overlays under writable paths.`);
741
743
  }
742
744
  function pathsOverlap(first, second) {
743
- const left = normalizePathForComparison(first);
744
- const right = normalizePathForComparison(second);
745
+ const left = normalizeMxcPathForComparison(first);
746
+ const right = normalizeMxcPathForComparison(second);
745
747
  return isPathInside(left, right) || isPathInside(right, left);
746
748
  }
747
- function normalizePathForComparison(value) {
748
- const resolved = path.resolve(value);
749
- return process.platform === "win32" ? resolved.toLowerCase() : resolved;
750
- }
751
749
  function hostPathExists(candidatePath) {
752
750
  try {
753
751
  statSync(candidatePath);
@@ -1180,8 +1178,8 @@ function createMxcSandboxBackendHandle(params) {
1180
1178
  baseEnv: buildLauncherEnv(),
1181
1179
  input: execInput,
1182
1180
  maxOutputBytes: {
1183
- stdout: 10 * 1024 * 1024,
1184
- stderr: 10 * 1024 * 1024
1181
+ stdout: 10485760,
1182
+ stderr: 10485760
1185
1183
  },
1186
1184
  signal: cmdParams.signal,
1187
1185
  timeoutMs: 3e4
@@ -1329,11 +1327,11 @@ function registerMxcPlugin(api) {
1329
1327
  factory: createMxcSandboxBackendFactory(config),
1330
1328
  manager: mxcSandboxBackendManager
1331
1329
  });
1332
- api.registerService({
1330
+ api.lifecycle.registerRuntimeLifecycle({
1333
1331
  id: "mxc-sandbox-cleanup",
1334
- start() {},
1335
- stop() {
1336
- unregister();
1332
+ cleanup: ({ reason, sessionKey, runId }) => {
1333
+ if (sessionKey !== void 0 || runId !== void 0) return;
1334
+ if (reason === "disable" || reason === "restart") unregister();
1337
1335
  }
1338
1336
  });
1339
1337
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/mxc-sandbox",
3
- "version": "2026.9.1-beta.1",
3
+ "version": "2026.9.2",
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.9.1-beta.1"
24
+ "pluginApi": ">=2026.9.2"
25
25
  },
26
26
  "build": {
27
- "openclawVersion": "2026.9.1-beta.1",
27
+ "openclawVersion": "2026.9.2",
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.9.1-beta.1"
50
+ "openclaw": ">=2026.9.2"
51
51
  },
52
52
  "peerDependenciesMeta": {
53
53
  "openclaw": {