
















@@ -1,6 +1,9 @@
11import { EventEmitter } from "node:events";
2+import fs from "node:fs";
3+import os from "node:os";
4+import path from "node:path";
25import { Readable } from "node:stream";
3-import { beforeEach, describe, expect, it, vi } from "vitest";
6+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
47import {
58computeSandboxConfigHash,
69SANDBOX_DOCKER_EXPLICIT_ENV_POLICY_EPOCH,
@@ -32,6 +35,14 @@ const registryMocks = vi.hoisted(() => ({
3235updateRegistry: vi.fn(),
3336}));
343738+const tmpDirs: string[] = [];
39+40+function makeTempDir(): string {
41+const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-docker-mounts-"));
42+tmpDirs.push(dir);
43+return dir;
44+}
45+3546vi.mock("./registry.js", () => ({
3647readRegistryEntry: registryMocks.readRegistryEntry,
3748updateRegistry: registryMocks.updateRegistry,
@@ -184,6 +195,12 @@ async function ensureSandboxCreateCallForTest(params: {
184195}
185196186197describe("ensureSandboxContainer config-hash recreation", () => {
198+afterEach(() => {
199+for (const dir of tmpDirs.splice(0)) {
200+fs.rmSync(dir, { recursive: true, force: true });
201+}
202+});
203+187204beforeEach(async () => {
188205spawnState.calls.length = 0;
189206spawnState.inspectRunning = true;
@@ -205,13 +222,15 @@ describe("ensureSandboxContainer config-hash recreation", () => {
205222 workspaceDir,
206223agentWorkspaceDir: workspaceDir,
207224mountFormatVersion: SANDBOX_MOUNT_FORMAT_VERSION,
225+readOnlyWorkspaceSkillMounts: [],
208226});
209227const newHash = computeSandboxConfigHash({
210228docker: newCfg.docker,
211229workspaceAccess: newCfg.workspaceAccess,
212230 workspaceDir,
213231agentWorkspaceDir: workspaceDir,
214232mountFormatVersion: SANDBOX_MOUNT_FORMAT_VERSION,
233+readOnlyWorkspaceSkillMounts: [],
215234});
216235expect(newHash).not.toBe(oldHash);
217236@@ -263,6 +282,7 @@ describe("ensureSandboxContainer config-hash recreation", () => {
263282 workspaceDir,
264283agentWorkspaceDir: workspaceDir,
265284mountFormatVersion: SANDBOX_MOUNT_FORMAT_VERSION,
285+readOnlyWorkspaceSkillMounts: [],
266286});
267287const newHash = computeSandboxConfigHash({
268288docker: cfg.docker,
@@ -271,6 +291,7 @@ describe("ensureSandboxContainer config-hash recreation", () => {
271291 workspaceDir,
272292agentWorkspaceDir: workspaceDir,
273293mountFormatVersion: SANDBOX_MOUNT_FORMAT_VERSION,
294+readOnlyWorkspaceSkillMounts: [],
274295});
275296expect(newHash).not.toBe(oldHash);
276297@@ -307,6 +328,7 @@ describe("ensureSandboxContainer config-hash recreation", () => {
307328 workspaceDir,
308329agentWorkspaceDir: workspaceDir,
309330mountFormatVersion: SANDBOX_MOUNT_FORMAT_VERSION,
331+readOnlyWorkspaceSkillMounts: [],
310332});
311333312334spawnState.inspectRunning = false;
@@ -330,6 +352,38 @@ describe("ensureSandboxContainer config-hash recreation", () => {
330352expect(customMountIdx).toBeGreaterThan(workspaceMountIdx);
331353});
332354355+it("applies read-only skill overlays after custom binds", async () => {
356+const workspaceDir = makeTempDir();
357+const customRoot = makeTempDir();
358+fs.mkdirSync(path.join(workspaceDir, "skills", "demo"), { recursive: true });
359+fs.mkdirSync(customRoot, { recursive: true });
360+const cfg = createSandboxConfig([], [`${customRoot}:/workspace/skills:rw`]);
361+cfg.docker.dangerouslyAllowExternalBindSources = true;
362+363+spawnState.inspectRunning = false;
364+spawnState.labelHash = "stale-hash";
365+registryMocks.readRegistryEntry.mockResolvedValue({
366+containerName: "oc-test-shared",
367+sessionKey: "shared",
368+createdAtMs: 1,
369+lastUsedAtMs: 0,
370+image: cfg.docker.image,
371+configHash: "stale-hash",
372+});
373+374+const createCall = await ensureSandboxCreateCallForTest({ cfg, workspaceDir });
375+const bindArgs = collectDockerFlagValues(createCall.args, "-v");
376+const workspaceMountIdx = bindArgs.indexOf(`${workspaceDir}:/workspace:z`);
377+const customMountIdx = bindArgs.indexOf(`${customRoot}:/workspace/skills:rw`);
378+const protectedMountIdx = bindArgs.indexOf(
379+`${path.join(workspaceDir, "skills")}:/workspace/skills:ro,z`,
380+);
381+382+expect(workspaceMountIdx).toBeGreaterThanOrEqual(0);
383+expect(customMountIdx).toBeGreaterThan(workspaceMountIdx);
384+expect(protectedMountIdx).toBeGreaterThan(customMountIdx);
385+});
386+333387it.each([
334388{ workspaceAccess: "rw" as const, expectedMainMount: "/tmp/workspace:/workspace:z" },
335389{ workspaceAccess: "ro" as const, expectedMainMount: "/tmp/workspace:/workspace:ro,z" },
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。