diff --git a/studio/backend/core/inference/tools.py b/studio/backend/core/inference/tools.py index 541f6f530a..9d0fc53ae6 100644 --- a/studio/backend/core/inference/tools.py +++ b/studio/backend/core/inference/tools.py @@ -143,8 +143,7 @@ _BLOCKED_COMMANDS = ( else _BLOCKED_COMMANDS_COMMON ) -# Blocked commands that reach the network / another machine; hitting one steers -# the model to fetch via code or ask for an accessible path. +# Network/remote-reaching commands; blocking one steers the model to fetch via code or ask for a path. _NETWORK_BLOCKED_COMMANDS = frozenset( {"curl", "wget", "nc", "ncat", "netcat", "socat", "ssh", "scp", "sftp", "rsync"} ) @@ -2949,12 +2948,10 @@ WEB_SEARCH_TOOL = { }, } -# Appended to python/terminal descriptions: stop models writing to /mnt/data or -# guessing a local path for a repo the user mentioned but never uploaded. -# Split so the Bypass Permissions variant can drop the network sentence: under -# bypass, _python_exec/_bash_exec skip the safety analysis and curl/wget blocklist -# (there is no network namespace), so egress works and claiming those downloads -# are unavailable would falsely block a user-supplied remote resource. +# Appended to python/terminal descriptions to stop models writing to /mnt/data or +# guessing a local path. Split so the Bypass variant can drop the network sentence: +# under bypass egress works, so claiming downloads are unavailable would wrongly +# block a user-supplied remote resource. _SANDBOX_PATHS_NOTE_INTRO = ( " The working directory is an isolated scratch space that may already hold " "files from earlier work in this conversation or project, plus anything you " @@ -2980,13 +2977,11 @@ _SANDBOX_PATHS_NOTE_TAIL = ( "you need are not here, ask the user to provide them or an exact path " "instead of guessing one." ) -# Default (sandboxed) note: steers python at public sources, keeps the curl/wget -# restriction without overstating the host check as a hard wall. +# Default (sandboxed) note: steers python at public sources, keeps the curl/wget restriction. _SANDBOX_PATHS_NOTE = ( _SANDBOX_PATHS_NOTE_INTRO + _SANDBOX_PATHS_NOTE_NETWORK + _SANDBOX_PATHS_NOTE_TAIL ) -# Bypass variant: same guidance minus the network sentence (stays neutral rather -# than claiming egress works). +# Bypass variant: same guidance minus the network sentence. _SANDBOX_PATHS_NOTE_BYPASS = _SANDBOX_PATHS_NOTE_INTRO + _SANDBOX_PATHS_NOTE_TAIL PYTHON_TOOL = { @@ -3102,8 +3097,7 @@ def _with_sandbox_note(tool: dict, note: str) -> dict: return {**tool, "function": fn} -# Bypass variants: descriptions omit the curl/wget restriction, skipped when the -# sandbox is disabled (disable_sandbox = bypass_permissions in the tool loops). +# Bypass variants: descriptions omit the curl/wget restriction, used when the sandbox is disabled. PYTHON_TOOL_BYPASS = _with_sandbox_note(PYTHON_TOOL, _SANDBOX_PATHS_NOTE_BYPASS) TERMINAL_TOOL_BYPASS = _with_sandbox_note(TERMINAL_TOOL, _SANDBOX_PATHS_NOTE_BYPASS) _BYPASS_TOOL_OVERRIDES = { diff --git a/studio/backend/routes/inference.py b/studio/backend/routes/inference.py index 563ff04fbc..217551c9a8 100644 --- a/studio/backend/routes/inference.py +++ b/studio/backend/routes/inference.py @@ -2426,8 +2426,7 @@ _TOOL_CODE_TIP = ( "you need is not present, ask the user to provide it or give an exact path " "rather than running commands against a guessed one." ) -# Bypass variant: drops the "internet access is limited" clause (false when the -# sandbox is disabled), keeping the workdir-default framing. +# Bypass variant: drops the "internet access is limited" clause, keeps the workdir-default framing. _TOOL_CODE_TIP_BYPASS = ( "Use code execution for math, calculations, data processing, or to parse " "and analyze information from tool results. It runs in a sandbox whose " @@ -2518,8 +2517,7 @@ async def _select_request_tools( # Drop the RAG tool without a scope: nothing to search over. if not payload.rag_scope: tools = [t for t in tools if t["function"]["name"] != "search_knowledge_base"] - # A sandbox-disabled request runs python/terminal with disable_sandbox=True, so - # the descriptions must not claim the curl/wget block that no longer applies. + # Sandbox-disabled: descriptions must not claim the curl/wget block that no longer applies. if _sandbox_disabled(payload): tools = apply_bypass_tool_notes(tools) if mcp_allowed: @@ -12878,8 +12876,7 @@ async def anthropic_messages( requested_studio_tools, payload.enabled_tools, ) - # A sandbox-disabled request runs unsandboxed, so drop the curl/wget - # restriction from the python/terminal descriptions here too. + # Sandbox-disabled: drop the curl/wget restriction from the descriptions here too. if _sandbox_disabled(payload): openai_tools = apply_bypass_tool_notes(openai_tools) diff --git a/studio/backend/tests/test_anthropic_messages.py b/studio/backend/tests/test_anthropic_messages.py index fc0839ce47..16d602e9fa 100644 --- a/studio/backend/tests/test_anthropic_messages.py +++ b/studio/backend/tests/test_anthropic_messages.py @@ -174,8 +174,7 @@ class TestToolActionNudge: assert _build_tool_action_nudge(tools = [], model_name = "Llama-3.1-8B-Instruct") == "" def test_code_tip_does_not_name_a_disabled_sibling_code_tool(self): - # has_code fires for either python or terminal, so the code tip must stay - # tool-neutral: enabling one must never name the other as available. + # has_code fires for either python or terminal, so the code tip must stay tool-neutral (never name the sibling). py_only = _build_tool_action_nudge( tools = [{"type": "function", "function": {"name": "python"}}], model_name = "Llama-3.1-8B-Instruct", diff --git a/studio/backend/tests/test_sandbox_paths_note.py b/studio/backend/tests/test_sandbox_paths_note.py index 8cb68825cc..47567ed35a 100644 --- a/studio/backend/tests/test_sandbox_paths_note.py +++ b/studio/backend/tests/test_sandbox_paths_note.py @@ -55,8 +55,7 @@ def test_note_does_not_claim_project_sandbox_starts_empty(): def test_note_does_not_claim_local_files_are_inaccessible(): lowered = _SANDBOX_PATHS_NOTE.lower() - # No filesystem isolation on this branch, so an exact local path is readable; - # the note must frame the workdir as the default work location, not deny access. + # No filesystem isolation here, so the note frames the workdir as the default work location, not a denial of access. assert "cannot see the user's own computer" not in lowered assert "default location for your work" in lowered @@ -67,9 +66,8 @@ def test_note_is_appended_to_both_tool_descriptions(): def test_note_scopes_network_block_to_the_terminal_not_all_shell_commands(): - # The terminal keeps the network namespace and doesn't block git/pip, so the - # note names only the blocked commands (curl/wget) and puts the allowlist on the - # python tool rather than claiming shell network is fully blocked. + # Terminal keeps networking (git/pip work), so the note names only curl/wget and + # puts the allowlist on the python tool, not on all shell network. lowered = _SANDBOX_PATHS_NOTE.lower() assert "shell network commands are blocked" not in lowered assert "curl" in lowered and "wget" in lowered @@ -87,9 +85,8 @@ def test_note_distinguishes_attachments_from_sandbox_uploads(): def test_blocked_network_command_message_gates_code_fallback_on_tool_availability(): - # A blocked curl/wget must not claim the sandbox is offline, nor name a specific - # remedy tool (python may be absent from the schema -- an invalid call); the - # fallback is gated on a code-execution tool being enabled this turn. + # A blocked curl/wget must not claim the sandbox is offline nor name a specific remedy + # tool (python may be absent); the fallback is gated on a code tool being enabled this turn. msg = _bash_exec("curl https://raw.githubusercontent.com/foo/bar/main/x.py").lower() assert "blocked command" in msg assert "cannot reach other machines or remote hosts" not in msg @@ -99,9 +96,8 @@ def test_blocked_network_command_message_gates_code_fallback_on_tool_availabilit def test_blocked_network_command_message_scopes_claim_to_the_command(): - # The block is by command name; the terminal keeps networking (git/pip work), - # so a private host is still reachable. The message must attribute the block to - # the command name, not assert the destination is unreachable. + # The block is by command name and the terminal keeps networking (a private host is still + # reachable), so the message must attribute the block to the name, not call the host unreachable. msg = _bash_exec("wget http://10.0.0.5/internal/repo.tar.gz").lower() assert "by name" in msg assert "not reachable" not in msg @@ -173,9 +169,8 @@ def test_bypass_code_execution_nudge_drops_the_limited_internet_claim(): def test_code_execution_nudge_does_not_deny_local_file_access(): - # No filesystem isolation here, so an exact local path is readable; the nudge - # must frame the workdir as the default work location and still allow an exact - # path, not deny access to the user's computer. + # No filesystem isolation here, so the nudge frames the workdir as the default work + # location and still allows an exact path, rather than denying access. from routes.inference import _TOOL_CODE_TIP lowered = _TOOL_CODE_TIP.lower() @@ -185,9 +180,8 @@ def test_code_execution_nudge_does_not_deny_local_file_access(): def test_sandbox_disabled_treats_permission_mode_full_as_unsandboxed(): - # Both agent loops fold permission_mode "full" into disable_sandbox, so "full" - # runs python/terminal unsandboxed. _sandbox_disabled keys off the effective - # flag so the notes match what executes even if that fold is refactored away. + # Both agent loops fold permission_mode "full" into disable_sandbox, so "full" runs + # unsandboxed. _sandbox_disabled keys off the effective flag, not just the fold. from types import SimpleNamespace from routes.inference import _sandbox_disabled