security: address review findings -- domain check, nesting depth, SSE redaction

Three security fixes from code review:

1. CRITICAL: Block Container-domain tools from executing on the
   orchestrator host. The ToolExecutor now checks tool.domain() and
   rejects Container tools with PtcError::DomainBlocked, preventing
   sandbox escape / RCE.

2. MEDIUM: Floor client-provided nesting_depth at 1 instead of trusting
   the worker's value. A malicious worker can no longer send
   nesting_depth=0 to bypass MAX_NESTING_DEPTH.

3. MEDIUM: Redact tool parameters in SSE JobToolUse events to prevent
   leaking sensitive data (API keys, passwords) to web UI observers.

4. Python SDK: Always send timeout_secs to server and use server_timeout+5
   for client-side HTTP timeout to prevent premature client timeouts.

Regression test: test_container_domain_blocked verifies Container-domain
tools are rejected.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
This commit is contained in:
Zaki
2026-03-21 08:12:54 +00:00
committed by Claude
co-authored by Claude Opus 4.6
parent 348a445a37
commit 3ad91338f7
3 changed files with 81 additions and 10 deletions
+8 -5
View File
@@ -50,13 +50,13 @@ def _token():
return _env("IRONCLAW_WORKER_TOKEN")
def call_tool(name, params=None, timeout_secs=None):
def call_tool(name, params=None, timeout_secs=60):
"""Call a tool on the orchestrator by name.
Args:
name: Tool name (e.g., "echo", "shell", "read_file").
params: Dictionary of parameters to pass to the tool.
timeout_secs: Optional timeout in seconds (max 300).
timeout_secs: Timeout in seconds (default 60, max 300).
Returns:
Tool output as a string.
@@ -65,12 +65,12 @@ def call_tool(name, params=None, timeout_secs=None):
RuntimeError: If the tool call fails.
"""
url = f"{_base_url()}/tools/call"
server_timeout = min(int(timeout_secs), 300)
body = {
"tool_name": name,
"parameters": params or {},
"timeout_secs": server_timeout,
}
if timeout_secs is not None:
body["timeout_secs"] = min(int(timeout_secs), 300)
data = json.dumps(body).encode("utf-8")
req = urllib.request.Request(
@@ -84,7 +84,10 @@ def call_tool(name, params=None, timeout_secs=None):
)
try:
with urllib.request.urlopen(req, timeout=(timeout_secs if timeout_secs is not None else 60) + 5) as resp:
# Client-side timeout slightly longer than server-side to account
# for network latency, preventing premature client timeouts.
client_timeout = server_timeout + 5
with urllib.request.urlopen(req, timeout=client_timeout) as resp:
result = json.loads(resp.read().decode("utf-8"))
except urllib.error.HTTPError as e:
body_text = e.read().decode("utf-8", errors="replace") if e.fp else ""