diff --git a/core/chat_agent.py b/core/chat_agent.py index 9c28ef7..1157059 100644 --- a/core/chat_agent.py +++ b/core/chat_agent.py @@ -527,6 +527,15 @@ def run_cowork( preview = {"kind": "info", "title": name, "text": str(args)} emit({"type": "tool_proposed", "id": tc_id, "name": name, "args": args, "preview": preview}) + if ctx.block_network: + result = {"ok": False, "output": ( + f"{name}: network access is blocked by the Sandbox Security Layer " + '("Block network for agent-run commands" is on in Settings).')} + emit({"type": "tool_result", "id": tc_id, "name": name, + "ok": False, "output": result["output"]}) + messages.append({"role": "tool", "tool_call_id": tc_id, "name": name, + "content": result["output"]}) + continue # R05-T04: MCP/connector tools used to run with NO permission # check at all — this is what closes that gap. Same policy, # same gate object as the built-in tools below. diff --git a/core/code_agent.py b/core/code_agent.py index 24752cb..a31cdc3 100644 --- a/core/code_agent.py +++ b/core/code_agent.py @@ -327,7 +327,12 @@ def run_code( else: emit({"type": "tool_start", "id": tc_id, "name": name}) if is_extra and extra_executor is not None: - result = extra_executor(name, args) + if ctx.block_network: + result = {"ok": False, "output": ( + f"{name}: network access is blocked by the Sandbox Security Layer " + '("Block network for agent-run commands" is on in Settings).')} + else: + result = extra_executor(name, args) else: def on_output(line: str, _id=tc_id, _name=name) -> None: emit({"type": "tool_output", "id": _id, "name": _name, "delta": line}) diff --git a/presentation/monitoring/tabs/tools_admin_tab.py b/presentation/monitoring/tabs/tools_admin_tab.py index 1499c50..41858cc 100644 --- a/presentation/monitoring/tabs/tools_admin_tab.py +++ b/presentation/monitoring/tabs/tools_admin_tab.py @@ -216,7 +216,8 @@ class ToolsAdminTab(QWidget): result. Respects the fetch_url toggle: when web access is OFF the agent cannot reach the internet, so the test reports that instead of probing.""" disabled = ("fetch_url" in self.ctx.config.tools_disabled - or not bool(self.ctx.config.agent_security.get("allow_url_fetch", True))) + or not bool(self.ctx.config.agent_security.get("allow_url_fetch", True)) + or bool(self.ctx.config.agent_security.get("block_network", False))) if disabled: self.test_internet_status.setText(tr("tools_admin.internet_disabled")) self.test_internet_status.setStyleSheet("color: #c00;") diff --git a/state.py b/state.py index 976be01..d1a9d6b 100644 --- a/state.py +++ b/state.py @@ -233,6 +233,10 @@ class AppContext: connections across calls/turns (spawning a subprocess per turn would be slow and wasteful). A server/connector that fails to connect is skipped, not a hard failure for the turn.""" + # Sandbox Security Layer blocks agent-owned network connectors before + # they can spawn a server or issue a REST request. + if self.config.agent_security.get("block_network", False): + return [], None # Master switch (Monitoring → Tools → Connector): when the admin turns # "Connect to external" off, the agent connects to NO external # connectors/MCP at all — no subprocesses spawned, no REST calls.