From c703aa0fe92df9fb71cf254fc46991e05fba2114 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 2 Mar 2026 23:23:56 +0000 Subject: [PATCH] fix(agents): align sandboxed ACP prompt guidance --- CHANGELOG.md | 2 +- src/agents/system-prompt.test.ts | 2 ++ src/agents/system-prompt.ts | 9 +++++---- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0de743d286..fec4691a379 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,7 +43,7 @@ Docs: https://docs.openclaw.ai - Media understanding/audio transcription guard: skip tiny/empty audio files (<1024 bytes) before provider/CLI transcription to avoid noisy invalid-audio failures and preserve clean fallback behavior. (#8388) Thanks @Glucksberg. - OpenAI media capabilities: include `audio` in the OpenAI provider capability list so audio transcription models are eligible in media-understanding provider selection. (#12717) Thanks @openjay. - 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. -- Security/ACP sandbox inheritance: enforce fail-closed runtime guardrails for `sessions_spawn` with `runtime="acp"` by rejecting ACP spawns from sandboxed requester sessions and rejecting `sandbox="require"` for ACP runtime, preventing sandbox-boundary bypass via host-side ACP initialization. (#32254) Thanks @dutifulbob. +- Security/ACP sandbox inheritance: enforce fail-closed runtime guardrails for `sessions_spawn` with `runtime="acp"` by rejecting ACP spawns from sandboxed requester sessions and rejecting `sandbox="require"` for ACP runtime, preventing sandbox-boundary bypass via host-side ACP initialization. (#32254) Thanks @tdjackey for reporting, and @dutifulbob for the fix. - 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. - Security/Webhook request hardening: enforce auth-before-body parsing for BlueBubbles and Google Chat webhook handlers, add strict pre-auth body/time budgets for webhook auth paths (including LINE signature verification), and add shared in-flight/request guardrails plus regression tests/lint checks to prevent reintroducing unauthenticated slow-body DoS patterns. Thanks @GCXWLP for reporting. - Gateway/Security hardening: tie loopback-origin dev allowance to actual local socket clients (not Host header claims), add explicit warnings/metrics when `gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback` accepts websocket origins, harden safe-regex detection for quantified ambiguous alternation patterns (for example `(a|aa)+`), and bound large regex-evaluation inputs for session-filter and log-redaction paths. diff --git a/src/agents/system-prompt.test.ts b/src/agents/system-prompt.test.ts index dea5c209b49..8a2d34c8e24 100644 --- a/src/agents/system-prompt.test.ts +++ b/src/agents/system-prompt.test.ts @@ -295,6 +295,8 @@ describe("buildAgentSystemPrompt", () => { }, }); + expect(prompt).not.toContain('runtime="acp" requires `agentId`'); + expect(prompt).not.toContain("ACP harness ids follow acp.allowedAgents"); expect(prompt).not.toContain( 'For requests like "do this in codex/claude code/gemini", treat it as ACP harness intent', ); diff --git a/src/agents/system-prompt.ts b/src/agents/system-prompt.ts index 7e709195a1b..97b8321ed15 100644 --- a/src/agents/system-prompt.ts +++ b/src/agents/system-prompt.ts @@ -233,6 +233,8 @@ export function buildAgentSystemPrompt(params: { memoryCitationsMode?: MemoryCitationsMode; }) { const acpEnabled = params.acpEnabled !== false; + const sandboxedRuntime = params.sandboxInfo?.enabled === true; + const acpSpawnRuntimeEnabled = acpEnabled && !sandboxedRuntime; const coreToolSummaries: Record = { read: "Read file contents", write: "Create or overwrite files", @@ -252,13 +254,13 @@ export function buildAgentSystemPrompt(params: { cron: "Manage cron jobs and wake events (use for reminders; when scheduling a reminder, write the systemEvent text as something that will read like a reminder when it fires, and mention that it is a reminder depending on the time gap between setting and firing; include recent context in reminder text if appropriate)", message: "Send messages and channel actions", gateway: "Restart, apply config, or run updates on the running OpenClaw process", - agents_list: acpEnabled + agents_list: acpSpawnRuntimeEnabled ? 'List OpenClaw agent ids allowed for sessions_spawn when runtime="subagent" (not ACP harness ids)' : "List OpenClaw agent ids allowed for sessions_spawn", sessions_list: "List other sessions (incl. sub-agents) with filters/last", sessions_history: "Fetch history for another session/sub-agent", sessions_send: "Send a message to another session/sub-agent", - sessions_spawn: acpEnabled + sessions_spawn: acpSpawnRuntimeEnabled ? 'Spawn an isolated sub-agent or ACP coding session (runtime="acp" requires `agentId` unless `acp.defaultAgent` is configured; ACP harness ids follow acp.allowedAgents, not agents_list)' : "Spawn an isolated sub-agent session", subagents: "List, steer, or kill sub-agent runs for this requester session", @@ -310,8 +312,7 @@ export function buildAgentSystemPrompt(params: { const normalizedTools = canonicalToolNames.map((tool) => tool.toLowerCase()); const availableTools = new Set(normalizedTools); const hasSessionsSpawn = availableTools.has("sessions_spawn"); - const sandboxedRuntime = params.sandboxInfo?.enabled === true; - const acpHarnessSpawnAllowed = hasSessionsSpawn && acpEnabled && !sandboxedRuntime; + const acpHarnessSpawnAllowed = hasSessionsSpawn && acpSpawnRuntimeEnabled; const externalToolSummaries = new Map(); for (const [key, value] of Object.entries(params.toolSummaries ?? {})) { const normalized = key.trim().toLowerCase();