Skip to content

Commit ab80721

Browse files
committed
fix(openshell): drop poisoned sandbox skills downloads
1 parent e733a8a commit ab80721

2 files changed

Lines changed: 48 additions & 3 deletions

File tree

extensions/openshell/src/backend.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -602,18 +602,24 @@ function resolveRemoteMaterializedSkillsWorkspaceDir(remoteWorkspaceDir: string)
602602

603603
async function removeMaterializedSkillsFromDownloadedWorkspace(tmpDir: string): Promise<void> {
604604
let cursor = tmpDir;
605-
for (const part of MATERIALIZED_SKILLS_REMOTE_PARTS) {
605+
for (const [index, part] of MATERIALIZED_SKILLS_REMOTE_PARTS.entries()) {
606606
const next = path.join(cursor, part);
607607
const stats = await fs.lstat(next).catch(() => null);
608-
if (!stats || stats.isSymbolicLink()) {
608+
if (!stats) {
609+
return;
610+
}
611+
if (index === MATERIALIZED_SKILLS_REMOTE_PARTS.length - 1) {
612+
await fs.rm(next, { recursive: true, force: true });
613+
return;
614+
}
615+
if (stats.isSymbolicLink()) {
609616
return;
610617
}
611618
if (!stats.isDirectory()) {
612619
return;
613620
}
614621
cursor = next;
615622
}
616-
await fs.rm(cursor, { recursive: true, force: true });
617623
}
618624

619625
async function moveMaterializedSkillsShadowAside(params: {

extensions/openshell/src/openshell-core.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,45 @@ describe("openshell backend manager", () => {
372372
renameSpy.mockRestore();
373373
}
374374
});
375+
376+
it("drops non-directory materialized sandbox skills from mirror downloads", async () => {
377+
const workspaceDir = await makeTempDir("openclaw-openshell-workspace-");
378+
cliMocks.runOpenShellCli.mockImplementation(async ({ args }: { args: string[] }) => {
379+
if (args[0] === "sandbox" && args[1] === "download") {
380+
const tmpDir = args[4];
381+
await fs.writeFile(path.join(tmpDir, "from-remote.txt"), "remote", "utf8");
382+
await fs.mkdir(path.join(tmpDir, ".openclaw"), { recursive: true });
383+
await fs.writeFile(path.join(tmpDir, ".openclaw", "sandbox-skills"), "poison", "utf8");
384+
}
385+
return { code: 0, stdout: "", stderr: "" };
386+
});
387+
388+
const factory = createOpenShellSandboxBackendFactory({
389+
pluginConfig: resolveOpenShellPluginConfig({
390+
command: "openshell",
391+
mode: "mirror",
392+
}),
393+
});
394+
const backend = await factory({
395+
sessionKey: "agent:main:turn",
396+
scopeKey: "agent:main",
397+
workspaceDir,
398+
agentWorkspaceDir: workspaceDir,
399+
cfg: createOpenShellBackendSandboxConfig(),
400+
});
401+
402+
await backend.finalizeExec?.({
403+
status: "completed",
404+
exitCode: 0,
405+
timedOut: false,
406+
token: undefined,
407+
});
408+
409+
await expect(fs.readFile(path.join(workspaceDir, "from-remote.txt"), "utf8")).resolves.toBe(
410+
"remote",
411+
);
412+
await expectPathMissing(path.join(workspaceDir, ".openclaw", "sandbox-skills"));
413+
});
375414
});
376415

377416
const tempDirs: string[] = [];

0 commit comments

Comments
 (0)