fix(sandbox): normalize protected mount destinations

This commit is contained in:
Vincent Koc
2026-08-01 11:41:36 +08:00
parent d1e69d9a35
commit 41ade36045
4 changed files with 11 additions and 5 deletions
@@ -599,6 +599,7 @@ describe("ensureSandboxContainer config-hash recreation", () => {
const customMount = `${customRoot}:/workspace/skills:rw`;
const cfg = createSandboxConfig([], [customMount]);
cfg.backend = backend;
cfg.docker.workdir = "/workspace/.";
cfg.docker.dangerouslyAllowExternalBindSources = true;
spawnState.inspectRunning = false;
registryMocks.readRegistryEntry.mockResolvedValue(null);
@@ -609,7 +610,7 @@ describe("ensureSandboxContainer config-hash recreation", () => {
expect(createCall.command).toBe(backend);
expect(bindArgs).not.toContain(customMount);
expect(bindArgs).toContain(`${path.join(workspaceDir, "skills")}:/workspace/skills:ro,z`);
expect(bindArgs).toContain(`${path.join(workspaceDir, "skills")}:/workspace/./skills:ro,z`);
},
);
+2
View File
@@ -214,8 +214,10 @@ describe("resolveSandboxFsPathWithMounts", () => {
const sandbox = createSandbox({
workspaceDir,
agentWorkspaceDir: workspaceDir,
containerWorkdir: "/workspace/.",
docker: {
...createSandbox().docker,
workdir: "/workspace/.",
binds: [`${customRoot}:/workspace/skills:rw`],
},
});
+1 -1
View File
@@ -265,7 +265,7 @@ describe("resolveProtectedSkillMountContainerPaths", () => {
it("returns container paths from skill mounts", () => {
const mounts: ReadOnlyWorkspaceSkillMount[] = [
{ hostPath: "/host/skills", containerPath: "/workspace/skills" },
{ hostPath: "/host/.agents/skills", containerPath: "/workspace/.agents/skills" },
{ hostPath: "/host/.agents/skills", containerPath: "/workspace/./.agents/skills/" },
];
const paths = resolveProtectedSkillMountContainerPaths(mounts);
expect(paths).toEqual(new Set(["/workspace/skills", "/workspace/.agents/skills"]));
+6 -3
View File
@@ -38,6 +38,10 @@ function containerJoin(root: string, ...parts: string[]): string {
return suffix ? `${normalizedRoot}/${suffix}` : normalizedRoot;
}
function normalizeMountContainerPath(containerPath: string): string {
return normalizeContainerPath(containerPath).replace(/\/+$/, "") || "/";
}
/** Hidden workspace used to materialize non-workspace skills for rw sandboxes. */
export function resolveMaterializedSandboxSkillsWorkspaceDir(rootDir: string): string {
return path.join(rootDir, ...MATERIALIZED_SANDBOX_SKILLS_WORKSPACE_PARTS);
@@ -126,7 +130,7 @@ export function formatReadOnlyWorkspaceSkillMountHashState(
export function resolveProtectedSkillMountContainerPaths(
mounts: readonly ReadOnlyWorkspaceSkillMount[],
): Set<string> {
return new Set(mounts.map((m) => m.containerPath));
return new Set(mounts.map((mount) => normalizeMountContainerPath(mount.containerPath)));
}
/**
@@ -151,8 +155,7 @@ export function filterBindsConflictingWithProtectedMounts(
filtered.push(bind);
continue;
}
// Strip trailing slashes so /workspace/skills/ matches /workspace/skills from containerJoin.
const containerPath = normalizeContainerPath(spec.container).replace(/\/+$/, "") || "/";
const containerPath = normalizeMountContainerPath(spec.container);
if (!protectedContainerPaths.has(containerPath)) {
filtered.push(bind);
}