mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 15:24:58 +00:00
fix: enforce sandbox workspace mount mode (#32227) (thanks @guanyu-zhang)
This commit is contained in:
@@ -27,6 +27,8 @@ Docs: https://docs.openclaw.ai
|
|||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
|
- Sandbox/workspace mount permissions: make primary `/workspace` bind mounts read-only whenever `workspaceAccess` is not `rw` (including `none`) across both core sandbox container and sandbox browser create flows. (#32227) Thanks @guanyu-zhang.
|
||||||
|
- Signal/message actions: allow `react` to fall back to `toolContext.currentMessageId` when `messageId` is omitted, matching Telegram behavior and unblocking agent-initiated reactions on inbound turns. (#32217) Thanks @dunamismax.
|
||||||
- Gateway/OpenAI chat completions: honor `x-openclaw-message-channel` when building `agentCommand` input for `/v1/chat/completions`, preserving caller channel identity instead of forcing `webchat`. (#30462) Thanks @bmendonca3.
|
- Gateway/OpenAI chat completions: honor `x-openclaw-message-channel` when building `agentCommand` input for `/v1/chat/completions`, preserving caller channel identity instead of forcing `webchat`. (#30462) Thanks @bmendonca3.
|
||||||
- Secrets/exec resolver timeout defaults: use provider `timeoutMs` as the default inactivity (`noOutputTimeoutMs`) watchdog for exec secret providers, preventing premature no-output kills for resolvers that start producing output after 2s. (#32235) Thanks @bmendonca3.
|
- Secrets/exec resolver timeout defaults: use provider `timeoutMs` as the default inactivity (`noOutputTimeoutMs`) watchdog for exec secret providers, preventing premature no-output kills for resolvers that start producing output after 2s. (#32235) Thanks @bmendonca3.
|
||||||
- Feishu/File upload filenames: percent-encode non-ASCII/special-character `file_name` values in Feishu multipart uploads so Chinese/symbol-heavy filenames are sent as proper attachments instead of plain text links. (#31179) Thanks @Kay-051.
|
- Feishu/File upload filenames: percent-encode non-ASCII/special-character `file_name` values in Feishu multipart uploads so Chinese/symbol-heavy filenames are sent as proper attachments instead of plain text links. (#31179) Thanks @Kay-051.
|
||||||
|
|||||||
@@ -184,4 +184,43 @@ describe("ensureSandboxBrowser create args", () => {
|
|||||||
);
|
);
|
||||||
expect(result?.noVncUrl).toBeUndefined();
|
expect(result?.noVncUrl).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("mounts the main workspace read-only when workspaceAccess is none", async () => {
|
||||||
|
const cfg = buildConfig(false);
|
||||||
|
cfg.workspaceAccess = "none";
|
||||||
|
|
||||||
|
await ensureSandboxBrowser({
|
||||||
|
scopeKey: "session:test",
|
||||||
|
workspaceDir: "/tmp/workspace",
|
||||||
|
agentWorkspaceDir: "/tmp/workspace",
|
||||||
|
cfg,
|
||||||
|
});
|
||||||
|
|
||||||
|
const createArgs = dockerMocks.execDocker.mock.calls.find(
|
||||||
|
(call: unknown[]) => Array.isArray(call[0]) && call[0][0] === "create",
|
||||||
|
)?.[0] as string[] | undefined;
|
||||||
|
|
||||||
|
expect(createArgs).toBeDefined();
|
||||||
|
expect(createArgs).toContain("/tmp/workspace:/workspace:ro");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps the main workspace writable when workspaceAccess is rw", async () => {
|
||||||
|
const cfg = buildConfig(false);
|
||||||
|
cfg.workspaceAccess = "rw";
|
||||||
|
|
||||||
|
await ensureSandboxBrowser({
|
||||||
|
scopeKey: "session:test",
|
||||||
|
workspaceDir: "/tmp/workspace",
|
||||||
|
agentWorkspaceDir: "/tmp/workspace",
|
||||||
|
cfg,
|
||||||
|
});
|
||||||
|
|
||||||
|
const createArgs = dockerMocks.execDocker.mock.calls.find(
|
||||||
|
(call: unknown[]) => Array.isArray(call[0]) && call[0][0] === "create",
|
||||||
|
)?.[0] as string[] | undefined;
|
||||||
|
|
||||||
|
expect(createArgs).toBeDefined();
|
||||||
|
expect(createArgs).toContain("/tmp/workspace:/workspace");
|
||||||
|
expect(createArgs).not.toContain("/tmp/workspace:/workspace:ro");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -83,11 +83,15 @@ vi.mock("node:child_process", async (importOriginal) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
function createSandboxConfig(dns: string[], binds?: string[]): SandboxConfig {
|
function createSandboxConfig(
|
||||||
|
dns: string[],
|
||||||
|
binds?: string[],
|
||||||
|
workspaceAccess: "rw" | "ro" | "none" = "rw",
|
||||||
|
): SandboxConfig {
|
||||||
return {
|
return {
|
||||||
mode: "all",
|
mode: "all",
|
||||||
scope: "shared",
|
scope: "shared",
|
||||||
workspaceAccess: "rw",
|
workspaceAccess,
|
||||||
workspaceRoot: "~/.openclaw/sandboxes",
|
workspaceRoot: "~/.openclaw/sandboxes",
|
||||||
docker: {
|
docker: {
|
||||||
image: "openclaw-sandbox:test",
|
image: "openclaw-sandbox:test",
|
||||||
@@ -245,4 +249,42 @@ describe("ensureSandboxContainer config-hash recreation", () => {
|
|||||||
expect(workspaceMountIdx).toBeGreaterThanOrEqual(0);
|
expect(workspaceMountIdx).toBeGreaterThanOrEqual(0);
|
||||||
expect(customMountIdx).toBeGreaterThan(workspaceMountIdx);
|
expect(customMountIdx).toBeGreaterThan(workspaceMountIdx);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
{ workspaceAccess: "rw" as const, expectedMainMount: "/tmp/workspace:/workspace" },
|
||||||
|
{ workspaceAccess: "ro" as const, expectedMainMount: "/tmp/workspace:/workspace:ro" },
|
||||||
|
{ workspaceAccess: "none" as const, expectedMainMount: "/tmp/workspace:/workspace:ro" },
|
||||||
|
])(
|
||||||
|
"uses expected main mount permissions when workspaceAccess=$workspaceAccess",
|
||||||
|
async ({ workspaceAccess, expectedMainMount }) => {
|
||||||
|
const workspaceDir = "/tmp/workspace";
|
||||||
|
const cfg = createSandboxConfig([], undefined, workspaceAccess);
|
||||||
|
|
||||||
|
spawnState.inspectRunning = false;
|
||||||
|
spawnState.labelHash = "";
|
||||||
|
registryMocks.readRegistry.mockResolvedValue({ entries: [] });
|
||||||
|
registryMocks.updateRegistry.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
await ensureSandboxContainer({
|
||||||
|
sessionKey: "agent:main:session-1",
|
||||||
|
workspaceDir,
|
||||||
|
agentWorkspaceDir: workspaceDir,
|
||||||
|
cfg,
|
||||||
|
});
|
||||||
|
|
||||||
|
const createCall = spawnState.calls.find(
|
||||||
|
(call) => call.command === "docker" && call.args[0] === "create",
|
||||||
|
);
|
||||||
|
expect(createCall).toBeDefined();
|
||||||
|
|
||||||
|
const bindArgs: string[] = [];
|
||||||
|
const args = createCall?.args ?? [];
|
||||||
|
for (let i = 0; i < args.length; i += 1) {
|
||||||
|
if (args[i] === "-v" && typeof args[i + 1] === "string") {
|
||||||
|
bindArgs.push(args[i + 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(bindArgs).toContain(expectedMainMount);
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user