Root cause
Authentication & access control
feat: native image injection for vision-capable models
How AI contributed: New attack surfacePublished Mar 4, 2026 in
openclaw/openclawAll 35 findings in this repository
Summary In OpenClaw, the sandboxed image tool did not honor tools.fs.workspaceOnly=true for mounted paths resolved by the sandbox FS bridge. This allowed reading out-of-workspace mounted images (for example /agent/*) and forwarding those bytes to vision model providers. Impact Sandbox boundary bypass with confidentiality impact. In affected versions, read/write/edit respected workspace-only guardrails, but image could still load mounted out-of-workspace files and exfiltrate them via model requests. Affected Packages / Versions · Package: openclaw (npm) · Affected versions: <= 2026.2.22-2 · Patched versions: >= 2026.2.23 (released) · Latest published npm at triage time: 2026.2.22-2 Technical Details workspaceOnly was enforced in sandbox file tools and apply_patch, but not propagated/enforced for image sandbox path resolution. The fix threads workspaceOnly into image-tool construction and asserts sandbox-root containment before loading media bytes. Fix Commit(s) · dd9d9c1c609dcb4579f9e57bd7b5c879d0146b53 OpenClaw thanks @tdjackey for reporting.
The sandboxed image tool resolved mounted paths without applying tools.fs.workspaceOnly, so resolveSandboxedImagePath could load images outside the workspace even though read, write and edit enforced the same restriction. Those bytes then reached vision model providers, turning the bypass into exfiltration. The fix threads workspaceOnly into image-tool construction and asserts sandbox-root containment with assertSandboxPath.
Root cause
feat: native image injection for vision-capable models
Fix
fix(security): enforce workspaceOnly for sandbox image tool
Case facts
diff --git a/src/agents/tools/image-tool.ts b/src/agents/tools/image-tool.tsindex 52627e5474c..119423353c3 100644--- a/src/agents/tools/image-tool.ts+++ b/src/agents/tools/image-tool.ts@@ -145,11 +145,10 @@ export function resolveImageModelConfigForTool(params: { cfg?: ClawdbotConfig; agentDir: string; }): ImageModelConfig | null {- const primarySupportsImages = resolvePrimaryModelSupportsImages({- cfg: params.cfg,- agentDir: params.agentDir,- });- if (primarySupportsImages === true) return null;+ // Note: We intentionally do NOT gate based on primarySupportsImages here.+ // Even when the primary model supports images, we keep the tool available+ // because images are auto-injected into prompts (see attempt.ts detectAndLoadPromptImages).+ // The tool description is adjusted via modelHasVision to discourage redundant usage. const explicit = coerceImageModelConfig(params.cfg); if (explicit.primary?.trim() || (explicit.fallbacks?.length ?? 0) > 0) { return explicit;@@ -368,6 +367,8 @@ export function createImageTool(options?: { config?: ClawdbotConfig; agentDir?: string; sandboxRoot?: string;+ /** If true, the model has native vision capability and images in the prompt are auto-injected */+ modelHasVision?: boolean; }): AnyAgentTool | null { const agentDir = options?.agentDir?.trim(); if (!agentDir) {@@ -382,11 +383,17 @@ export function createImageTool(options?: { agentDir, }); if (!imageModelConfig) return null;++ // If model has native vision, images in the prompt are auto-injected+ // so this tool is only needed when image wasn't provided in the prompt+ const description = options?.modelHasVision+ ? "Analyze an image with a vision model. Only use this tool when the image was NOT already provided in the user's message. Images mentioned in the prompt are automatically visible to you."+ : "Analyze an image with the configured image model (agents.defaults.imageModel). Provide a prompt and image path or URL.";+ return { label: "Image", name: "image",- description:- "Analyze an image with the configured image model (agents.defaults.imageModel). Provide a prompt and image path or URL.",+ description, parameters: Type.Object({ prompt: Type.Optional(Type.String()), image: Type.String(),diff --git a/src/agents/tools/image-tool.ts b/src/agents/tools/image-tool.tsindex f27f9bdaaaf..872c95f8653 100644--- a/src/agents/tools/image-tool.ts+++ b/src/agents/tools/image-tool.ts@@ -14,0 +15 @@ import { discoverAuthStorage, discoverModels } from "../pi-model-discovery.js";+import { assertSandboxPath } from "../sandbox-paths.js";@@ -209,0 +211 @@ type ImageSandboxConfig = {+ workspaceOnly?: boolean;@@ -222,0 +225,7 @@ async function resolveSandboxedImagePath(params: {+ if (params.sandbox.workspaceOnly) {+ await assertSandboxPath({+ filePath: resolved.hostPath,+ cwd: params.sandbox.root,+ root: params.sandbox.root,+ });+ }@@ -241,0 +251,7 @@ async function resolveSandboxedImagePath(params: {+ if (params.sandbox.workspaceOnly) {+ await assertSandboxPath({+ filePath: out.hostPath,+ cwd: params.sandbox.root,+ root: params.sandbox.root,+ });+ }@@ -338,0 +355 @@ export function createImageTool(options?: {+ workspaceOnly?: boolean;@@ -447 +464,5 @@ export function createImageTool(options?: {- ? { root: options.sandbox.root.trim(), bridge: options.sandbox.bridge }+ ? {+ root: options.sandbox.root.trim(),+ bridge: options.sandbox.bridge,+ workspaceOnly: options.workspaceOnly === true,+ }AI-assisted change 9545e715bbc7f5c1dbbd54a56d4715c3dee9b56fe110f99f4883f0c1bb64151c · Fix 4a9737a13220e67b2aa41a7c371454ab01763023f7b8182cf8e0181f802bbb83