import { listAgentEntries } from "../agents/agent-scope-config.js"; // Resolves filesystem policy for exec and sandbox tool use. import { resolveConfiguredToolPolicies } from "../agents/agent-tools.policy.js"; import { resolveSandboxConfigForAgent } from "../agents/sandbox/config.js"; import { isToolAllowedByPolicies } from "../agents/tool-policy-match.js"; import type { OpenClawConfig } from "../config/config.js"; import type { AgentToolsConfig, ExecToolConfig } from "../config/types.tools.js"; const MUTATING_FS_TOOLS = ["write", "edit", "apply_patch"] as const; const RUNTIME_TOOLS = ["exec", "process"] as const; /** Scope where exec-like tools remain available while mutating filesystem tools are disabled. */ type ExecFilesystemPolicyDriftHit = { scopeLabel: string; runtimeTools: string[]; disabledFilesystemTools: string[]; sandboxMode: "off" | "non-main" | "all"; sandboxWorkspaceAccess: "none" | "ro" | "rw"; execHost: NonNullable; }; function resolveExecHost(params: { globalExec?: ExecToolConfig; agentExec?: ExecToolConfig; }): NonNullable { return params.agentExec?.host ?? params.globalExec?.host ?? "auto"; } function isExecFilesystemConstrained(params: { sandboxMode: "off" | "non-main" | "all"; sandboxWorkspaceAccess: "none" | "ro" | "rw"; execHost: NonNullable; }): boolean { if (params.sandboxMode !== "all") { return false; } if (params.execHost === "gateway" || params.execHost === "node") { return false; } return params.sandboxWorkspaceAccess !== "rw"; } /** Find policy scopes where exec can still mutate files despite disabled fs tools. */ export function collectExecFilesystemPolicyDriftHits( cfg: OpenClawConfig, ): ExecFilesystemPolicyDriftHit[] { const hits: ExecFilesystemPolicyDriftHit[] = []; const globalExec = cfg.tools?.exec; const contexts: Array<{ scopeLabel: string; agentId?: string; tools?: AgentToolsConfig; }> = [{ scopeLabel: "tools" }]; for (const agent of listAgentEntries(cfg)) { if (!agent || typeof agent !== "object" || typeof agent.id !== "string") { continue; } contexts.push({ scopeLabel: `agents.entries.${agent.id}.tools`, agentId: agent.id, tools: agent.tools, }); } for (const context of contexts) { const sandbox = resolveSandboxConfigForAgent(cfg, context.agentId); const execHost = resolveExecHost({ globalExec, agentExec: context.tools?.exec, }); // Sandboxed all-mode with non-rw workspace access constrains local exec // mutations enough that disabling write/edit/apply_patch is not misleading. if ( isExecFilesystemConstrained({ sandboxMode: sandbox.mode, sandboxWorkspaceAccess: sandbox.workspaceAccess, execHost, }) ) { continue; } const policies = resolveConfiguredToolPolicies({ cfg, agentTools: context.tools, sandboxMode: sandbox.mode, agentId: context.agentId, }); const runtimeTools = RUNTIME_TOOLS.filter((tool) => isToolAllowedByPolicies(tool, policies)); if (!runtimeTools.includes("exec")) { continue; } // Drift means every explicit mutating filesystem tool is disabled while a // runtime path that can still mutate files remains allowed. const disabledFilesystemTools = MUTATING_FS_TOOLS.filter( (tool) => !isToolAllowedByPolicies(tool, policies), ); if (disabledFilesystemTools.length !== MUTATING_FS_TOOLS.length) { continue; } hits.push({ scopeLabel: context.scopeLabel, runtimeTools, disabledFilesystemTools, sandboxMode: sandbox.mode, sandboxWorkspaceAccess: sandbox.workspaceAccess, execHost, }); } return hits; }