@openclaw/openshell-sandbox 2026.8.2 → 2026.9.1
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 +28 -76
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
2
|
-
import { buildRemoteCommand, buildRemoteWorkdirValidationCommand, buildValidatedExecRemoteCommand, createRemoteShellSandboxFsBridge, createSshSandboxSessionFromConfigText, createWritableRenameTargetResolver, disposeSshSandboxSession, prepareSshSandboxExec, registerSandboxBackend, resolvePreferredOpenClawTmpDir, runPluginCommandWithTimeout, runSshSandboxCommand, sanitizeEnvVars, shellEscape, withTempWorkspace } from "openclaw/plugin-sdk/sandbox";
|
|
2
|
+
import { buildRemoteCommand, buildRemoteWorkdirValidationCommand, buildValidatedExecRemoteCommand, createRemoteShellSandboxFsBridge, createSshSandboxSessionFromConfigText, createWritableRenameTargetResolver, disposeSshSandboxSession, prepareSshSandboxExec, registerSandboxBackend, resolvePreferredOpenClawTmpDir, resolveReadOnlyWorkspaceSkillMounts, runPluginCommandWithTimeout, runSshSandboxCommand, sanitizeEnvVars, shellEscape, withTempWorkspace } from "openclaw/plugin-sdk/sandbox";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
4
|
import fsPromises from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
@@ -175,11 +175,6 @@ function resolveOpenShellWorkspaceRoot(roots, candidate) {
|
|
|
175
175
|
}
|
|
176
176
|
//#endregion
|
|
177
177
|
//#region extensions/openshell/src/fs-bridge.ts
|
|
178
|
-
const MATERIALIZED_SKILLS_CONTAINER_PARTS = [
|
|
179
|
-
".openclaw",
|
|
180
|
-
"sandbox-skills",
|
|
181
|
-
"skills"
|
|
182
|
-
];
|
|
183
178
|
function createOpenShellFsBridge(params) {
|
|
184
179
|
return new OpenShellFsBridge(params.sandbox, params.backend);
|
|
185
180
|
}
|
|
@@ -187,7 +182,7 @@ var OpenShellFsBridge = class {
|
|
|
187
182
|
constructor(sandbox, backend) {
|
|
188
183
|
this.sandbox = sandbox;
|
|
189
184
|
this.backend = backend;
|
|
190
|
-
this.resolveRenameTargets = createWritableRenameTargetResolver((target) => this.resolveTarget(target), (target, action) => this.ensureWritable(target, action));
|
|
185
|
+
this.resolveRenameTargets = createWritableRenameTargetResolver((target) => this.resolveTarget(target), (target, action) => this.ensureWritable(target, action, true));
|
|
191
186
|
}
|
|
192
187
|
resolvePath(params) {
|
|
193
188
|
const target = this.resolveTarget(params);
|
|
@@ -277,7 +272,7 @@ var OpenShellFsBridge = class {
|
|
|
277
272
|
async remove(params) {
|
|
278
273
|
const target = this.resolveTarget(params);
|
|
279
274
|
const hostPath = this.requireHostPath(target);
|
|
280
|
-
this.ensureWritable(target, "remove files");
|
|
275
|
+
this.ensureWritable(target, "remove files", params.recursive);
|
|
281
276
|
await assertLocalPathSafety({
|
|
282
277
|
target,
|
|
283
278
|
root: target.mountHostRoot,
|
|
@@ -344,8 +339,16 @@ var OpenShellFsBridge = class {
|
|
|
344
339
|
mtimeMs: stats.mtimeMs
|
|
345
340
|
};
|
|
346
341
|
}
|
|
347
|
-
ensureWritable(target, action) {
|
|
348
|
-
if (this.sandbox.workspaceAccess
|
|
342
|
+
ensureWritable(target, action, includeDescendants = false) {
|
|
343
|
+
if (this.sandbox.workspaceAccess === "ro" || !target.writable || includeDescendants && this.readOnlyMounts().some((mount) => isOpenShellRemotePathInside(target.containerPath, mount.containerPath))) throw new Error(`Sandbox path is read-only; cannot ${action}: ${target.containerPath}`);
|
|
344
|
+
}
|
|
345
|
+
readOnlyMounts() {
|
|
346
|
+
const workdirs = [this.sandbox.containerWorkdir];
|
|
347
|
+
if (this.sandbox.workspaceAccess !== "none" && path.resolve(this.sandbox.workspaceDir) !== path.resolve(this.sandbox.agentWorkspaceDir)) workdirs.push(this.backend.remoteAgentWorkspaceDir || "/agent");
|
|
348
|
+
return [...workdirs.flatMap((workdir) => resolveReadOnlyWorkspaceSkillMounts({
|
|
349
|
+
...this.sandbox,
|
|
350
|
+
workdir
|
|
351
|
+
})), ...this.sandbox.readOnlyResourceMounts ?? []];
|
|
349
352
|
}
|
|
350
353
|
requireHostPath(target) {
|
|
351
354
|
if (!target.hostPath) throw new Error(`OpenShell mirror bridge requires a local host path: ${target.containerPath}`);
|
|
@@ -357,24 +360,14 @@ var OpenShellFsBridge = class {
|
|
|
357
360
|
const hasAgentMount = this.sandbox.workspaceAccess !== "none" && workspaceRoot !== agentRoot;
|
|
358
361
|
const agentContainerRoot = (this.backend.remoteAgentWorkspaceDir || "/agent").replace(/\\/g, "/");
|
|
359
362
|
const workspaceContainerRoot = this.sandbox.containerWorkdir.replace(/\\/g, "/");
|
|
360
|
-
const skillsRoot = this.sandbox.skillsWorkspaceDir ? path.resolve(this.sandbox.skillsWorkspaceDir, "skills") : void 0;
|
|
361
|
-
const skillsContainerRoot = path.posix.join(workspaceContainerRoot, ...MATERIALIZED_SKILLS_CONTAINER_PARTS);
|
|
362
|
-
const workspaceSkillsShadowRoot = path.resolve(workspaceRoot, ...MATERIALIZED_SKILLS_CONTAINER_PARTS);
|
|
363
363
|
const input = params.filePath.trim();
|
|
364
|
-
|
|
365
|
-
const protectedSkillTarget = resolveProtectedSkillTarget({
|
|
366
|
-
input,
|
|
367
|
-
skillsRoot,
|
|
368
|
-
skillsContainerRoot
|
|
369
|
-
});
|
|
370
|
-
if (protectedSkillTarget) return protectedSkillTarget;
|
|
371
|
-
}
|
|
364
|
+
const readOnlyMounts = this.readOnlyMounts();
|
|
372
365
|
const containerMounts = [{
|
|
373
366
|
remote: workspaceContainerRoot,
|
|
374
367
|
owner: "workspace",
|
|
375
368
|
value: {
|
|
376
369
|
hostRoot: workspaceRoot,
|
|
377
|
-
writable: this.sandbox.workspaceAccess
|
|
370
|
+
writable: this.sandbox.workspaceAccess !== "ro"
|
|
378
371
|
}
|
|
379
372
|
}, ...hasAgentMount ? [{
|
|
380
373
|
remote: agentContainerRoot,
|
|
@@ -384,6 +377,14 @@ var OpenShellFsBridge = class {
|
|
|
384
377
|
writable: this.sandbox.workspaceAccess === "rw"
|
|
385
378
|
}
|
|
386
379
|
}] : []];
|
|
380
|
+
containerMounts.unshift(...readOnlyMounts.map((mount) => ({
|
|
381
|
+
remote: mount.containerPath,
|
|
382
|
+
owner: resolveOpenShellWorkspaceRoot(containerMounts, mount.containerPath)?.owner ?? "workspace",
|
|
383
|
+
value: {
|
|
384
|
+
hostRoot: path.resolve(mount.hostPath),
|
|
385
|
+
writable: false
|
|
386
|
+
}
|
|
387
|
+
})));
|
|
387
388
|
const resolveContainerTarget = (containerPath) => {
|
|
388
389
|
const containerMount = resolveOpenShellWorkspaceRoot(containerMounts, containerPath);
|
|
389
390
|
if (!containerMount) return;
|
|
@@ -392,11 +393,10 @@ var OpenShellFsBridge = class {
|
|
|
392
393
|
if (!isPathInside(containerMount.value.hostRoot, hostPath)) throw new Error(`Sandbox path escapes allowed mounts; cannot access: ${input}`);
|
|
393
394
|
return {
|
|
394
395
|
hostPath,
|
|
395
|
-
relativePath: containerMount.owner === "agent" ? relative ? containerMount.remote + "/" + relative : containerMount.remote : relative,
|
|
396
|
+
relativePath: containerMount.owner === "agent" ? relative ? containerMount.remote + "/" + relative : containerMount.remote : path.posix.relative(workspaceContainerRoot, containerPath),
|
|
396
397
|
containerPath: relative ? path.posix.join(containerMount.remote, relative) : containerMount.remote,
|
|
397
398
|
mountHostRoot: containerMount.value.hostRoot,
|
|
398
|
-
writable: containerMount.value.writable
|
|
399
|
-
source: containerMount.owner
|
|
399
|
+
writable: containerMount.value.writable
|
|
400
400
|
};
|
|
401
401
|
};
|
|
402
402
|
const containerCwd = params.cwd?.replace(/\\/g, "/");
|
|
@@ -409,30 +409,14 @@ var OpenShellFsBridge = class {
|
|
|
409
409
|
}
|
|
410
410
|
const cwd = params.cwd ? path.resolve(params.cwd) : workspaceRoot;
|
|
411
411
|
const hostPath = path.isAbsolute(input) ? path.resolve(input) : path.resolve(cwd, input);
|
|
412
|
-
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
workspaceSkillsShadowRoot,
|
|
416
|
-
skillsRoot,
|
|
417
|
-
skillsContainerRoot
|
|
418
|
-
});
|
|
419
|
-
if (protectedSkillShadowTarget) return protectedSkillShadowTarget;
|
|
412
|
+
for (const mount of readOnlyMounts) if (isPathInside(mount.hostPath, hostPath)) {
|
|
413
|
+
const relative = path.relative(mount.hostPath, hostPath).split(path.sep).join(path.posix.sep);
|
|
414
|
+
return expectResolvedContainerTarget(resolveContainerTarget(path.posix.join(mount.containerPath, relative)), input);
|
|
420
415
|
}
|
|
421
416
|
if (isPathInside(workspaceRoot, hostPath)) {
|
|
422
417
|
const relative = path.relative(workspaceRoot, hostPath).split(path.sep).join(path.posix.sep);
|
|
423
418
|
return expectResolvedContainerTarget(resolveContainerTarget(path.posix.join(workspaceContainerRoot, relative)), input);
|
|
424
419
|
}
|
|
425
|
-
if (skillsRoot && this.sandbox.workspaceAccess === "rw" && isPathInside(skillsRoot, hostPath)) {
|
|
426
|
-
const relative = path.relative(skillsRoot, hostPath).split(path.sep).join(path.posix.sep);
|
|
427
|
-
return {
|
|
428
|
-
hostPath,
|
|
429
|
-
relativePath: relative ? path.posix.join(...MATERIALIZED_SKILLS_CONTAINER_PARTS, relative) : path.posix.join(...MATERIALIZED_SKILLS_CONTAINER_PARTS),
|
|
430
|
-
containerPath: relative ? path.posix.join(skillsContainerRoot, relative) : skillsContainerRoot,
|
|
431
|
-
mountHostRoot: skillsRoot,
|
|
432
|
-
writable: false,
|
|
433
|
-
source: "protectedSkill"
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
420
|
if (hasAgentMount && isPathInside(agentRoot, hostPath)) {
|
|
437
421
|
const relative = path.relative(agentRoot, hostPath).split(path.sep).join(path.posix.sep);
|
|
438
422
|
return expectResolvedContainerTarget(resolveContainerTarget(path.posix.join(agentContainerRoot, relative)), input);
|
|
@@ -530,38 +514,6 @@ async function nearestExistingDirectoryStats(params) {
|
|
|
530
514
|
function isNotFoundError(err) {
|
|
531
515
|
return err instanceof FsSafeError && err.code === "not-found" || typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
|
|
532
516
|
}
|
|
533
|
-
function resolveProtectedSkillTarget(params) {
|
|
534
|
-
const relativeRoot = path.posix.join(...MATERIALIZED_SKILLS_CONTAINER_PARTS);
|
|
535
|
-
const normalizedInput = path.posix.normalize(params.input.replace(/\\/g, "/"));
|
|
536
|
-
const isAbsoluteContainer = normalizedInput === params.skillsContainerRoot || normalizedInput.startsWith(`${params.skillsContainerRoot}/`);
|
|
537
|
-
const isRelativeContainer = normalizedInput === relativeRoot || normalizedInput.startsWith(`${relativeRoot}/`);
|
|
538
|
-
if (!isAbsoluteContainer && !isRelativeContainer) return null;
|
|
539
|
-
const relative = isAbsoluteContainer ? path.posix.relative(params.skillsContainerRoot, normalizedInput) : path.posix.relative(relativeRoot, normalizedInput);
|
|
540
|
-
const safeRelative = relative === "." ? "" : relative;
|
|
541
|
-
return {
|
|
542
|
-
hostPath: safeRelative ? path.resolve(params.skillsRoot, ...safeRelative.split("/")) : params.skillsRoot,
|
|
543
|
-
relativePath: safeRelative ? path.posix.join(relativeRoot, safeRelative) : relativeRoot,
|
|
544
|
-
containerPath: safeRelative ? path.posix.join(params.skillsContainerRoot, safeRelative) : params.skillsContainerRoot,
|
|
545
|
-
mountHostRoot: params.skillsRoot,
|
|
546
|
-
writable: false,
|
|
547
|
-
source: "protectedSkill"
|
|
548
|
-
};
|
|
549
|
-
}
|
|
550
|
-
function resolveProtectedSkillShadowTarget(params) {
|
|
551
|
-
if (!isPathInside(params.workspaceSkillsShadowRoot, params.hostPath)) return null;
|
|
552
|
-
const relative = path.relative(params.workspaceSkillsShadowRoot, params.hostPath).split(path.sep).join(path.posix.sep);
|
|
553
|
-
const safeRelative = relative === "." ? "" : relative;
|
|
554
|
-
const hostPath = safeRelative ? path.resolve(params.skillsRoot, ...safeRelative.split("/")) : params.skillsRoot;
|
|
555
|
-
const relativeRoot = path.posix.join(...MATERIALIZED_SKILLS_CONTAINER_PARTS);
|
|
556
|
-
return {
|
|
557
|
-
hostPath,
|
|
558
|
-
relativePath: safeRelative ? path.posix.join(relativeRoot, safeRelative) : relativeRoot,
|
|
559
|
-
containerPath: safeRelative ? path.posix.join(params.skillsContainerRoot, safeRelative) : params.skillsContainerRoot,
|
|
560
|
-
mountHostRoot: params.skillsRoot,
|
|
561
|
-
writable: false,
|
|
562
|
-
source: "protectedSkill"
|
|
563
|
-
};
|
|
564
|
-
}
|
|
565
517
|
async function assertLocalPathSafety(params) {
|
|
566
518
|
if (!params.target.hostPath) throw new Error(`Missing local host path for ${params.target.containerPath}`);
|
|
567
519
|
const canonicalRoot = await fsPromises.realpath(params.root).catch(() => path.resolve(params.root));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/openshell-sandbox",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.9.1",
|
|
4
4
|
"description": "OpenClaw sandbox backend for the NVIDIA OpenShell CLI with mirrored local workspaces and SSH command execution.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
"minHostVersion": ">=2026.5.12-beta.1"
|
|
22
22
|
},
|
|
23
23
|
"compat": {
|
|
24
|
-
"pluginApi": ">=2026.
|
|
24
|
+
"pluginApi": ">=2026.9.1"
|
|
25
25
|
},
|
|
26
26
|
"build": {
|
|
27
|
-
"openclawVersion": "2026.
|
|
27
|
+
"openclawVersion": "2026.9.1",
|
|
28
28
|
"bundledDist": false
|
|
29
29
|
},
|
|
30
30
|
"release": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"README.md"
|
|
42
42
|
],
|
|
43
43
|
"peerDependencies": {
|
|
44
|
-
"openclaw": ">=2026.
|
|
44
|
+
"openclaw": ">=2026.9.1"
|
|
45
45
|
},
|
|
46
46
|
"peerDependenciesMeta": {
|
|
47
47
|
"openclaw": {
|