diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a7c6b7ffd5..025305b34ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Sandbox/Docker setup command parsing: accept `agents.*.sandbox.docker.setupCommand` as either a string or a string array, and normalize arrays to newline-delimited shell scripts so multi-step setup commands no longer concatenate without separators. (#31953) Thanks @liuxiaopai-ai. - Security/Node exec approvals: preserve shell/dispatch-wrapper argv semantics during approval hardening so approved wrapper commands (for example `env sh -c ...`) cannot drift into a different runtime command shape, and add regression coverage for both approval-plan generation and approved runtime execution paths. Thanks @tdjackey for reporting. - Sandbox/Bootstrap context boundary hardening: reject symlink/hardlink alias bootstrap seed files that resolve outside the source workspace and switch post-compaction `AGENTS.md` context reads to boundary-verified file opens, preventing host file content from being injected via workspace aliasing. Thanks @tdjackey for reporting. - Browser/Security output boundary hardening: replace check-then-rename output commits with root-bound fd-verified writes, unify install/skills canonical path-boundary checks, and add regression coverage for symlink-rebind race paths across browser output and shared fs-safe write flows. Thanks @tdjackey for reporting. diff --git a/src/config/config.sandbox-docker.test.ts b/src/config/config.sandbox-docker.test.ts index 138a254411d..56d041b180d 100644 --- a/src/config/config.sandbox-docker.test.ts +++ b/src/config/config.sandbox-docker.test.ts @@ -7,6 +7,26 @@ import { import { validateConfigObject } from "./config.js"; describe("sandbox docker config", () => { + it("joins setupCommand arrays with newlines", () => { + const res = validateConfigObject({ + agents: { + defaults: { + sandbox: { + docker: { + setupCommand: ["apt-get update", "apt-get install -y curl"], + }, + }, + }, + }, + }); + expect(res.ok).toBe(true); + if (res.ok) { + expect(res.config.agents?.defaults?.sandbox?.docker?.setupCommand).toBe( + "apt-get update\napt-get install -y curl", + ); + } + }); + it("accepts safe binds array in sandbox.docker config", () => { const res = validateConfigObject({ agents: { diff --git a/src/config/types.sandbox.ts b/src/config/types.sandbox.ts index b4d5e6e2027..047f10cde53 100644 --- a/src/config/types.sandbox.ts +++ b/src/config/types.sandbox.ts @@ -17,7 +17,7 @@ export type SandboxDockerSettings = { capDrop?: string[]; /** Extra environment variables for sandbox exec. */ env?: Record; - /** Optional setup command run once after container creation. */ + /** Optional setup command run once after container creation (array entries are joined by newline). */ setupCommand?: string; /** Limit container PIDs (0 = Docker default). */ pidsLimit?: number; diff --git a/src/config/zod-schema.agent-runtime.ts b/src/config/zod-schema.agent-runtime.ts index 63bec45b0ac..3f1ddb61e71 100644 --- a/src/config/zod-schema.agent-runtime.ts +++ b/src/config/zod-schema.agent-runtime.ts @@ -102,7 +102,10 @@ export const SandboxDockerSchema = z user: z.string().optional(), capDrop: z.array(z.string()).optional(), env: z.record(z.string(), z.string()).optional(), - setupCommand: z.string().optional(), + setupCommand: z + .union([z.string(), z.array(z.string())]) + .transform((value) => (Array.isArray(value) ? value.join("\n") : value)) + .optional(), pidsLimit: z.number().int().positive().optional(), memory: z.union([z.string(), z.number()]).optional(), memorySwap: z.union([z.string(), z.number()]).optional(),