@@ -329,6 +329,65 @@ def _build_safe_env(workdir: str) -> dict[str, str]:
329329 return env
330330
331331
332+ # Credential env vars dropped even in bypass mode so tool code cannot read the
333+ # operator's keys. Over-strips on purpose (a benign var is harmless to lose).
334+ _BYPASS_ENV_SECRET_NAMES = frozenset (
335+ {
336+ "HF_TOKEN" ,
337+ "HF_HUB_TOKEN" ,
338+ "HUGGING_FACE_HUB_TOKEN" ,
339+ "HUGGINGFACE_TOKEN" ,
340+ "HUGGINGFACEHUB_API_TOKEN" ,
341+ "WANDB_API_KEY" ,
342+ "GH_TOKEN" ,
343+ "GITHUB_TOKEN" ,
344+ "OPENAI_API_KEY" ,
345+ "ANTHROPIC_API_KEY" ,
346+ "GEMINI_API_KEY" ,
347+ "GOOGLE_API_KEY" ,
348+ "GROQ_API_KEY" ,
349+ "OPENROUTER_API_KEY" ,
350+ "REPLICATE_API_TOKEN" ,
351+ "COHERE_API_KEY" ,
352+ "MISTRAL_API_KEY" ,
353+ "NGC_API_KEY" ,
354+ "KAGGLE_KEY" ,
355+ "LD_PRELOAD" ,
356+ }
357+ )
358+ _BYPASS_ENV_SECRET_PREFIXES = ("AWS_" , "AZURE_" , "GOOGLE_" , "GCP_" , "GCLOUD_" , "DYLD_" )
359+ _BYPASS_ENV_SECRET_MARKERS = (
360+ "TOKEN" ,
361+ "API_KEY" ,
362+ "APIKEY" ,
363+ "SECRET" ,
364+ "PASSWORD" ,
365+ "PASSWD" ,
366+ "CREDENTIAL" ,
367+ "PRIVATE_KEY" ,
368+ )
369+
370+
371+ def _is_secret_env_name (name : str ) -> bool :
372+ """True if an env var name looks like it carries a credential."""
373+ upper = name .upper ()
374+ if upper in _BYPASS_ENV_SECRET_NAMES :
375+ return True
376+ if any (upper .startswith (p ) for p in _BYPASS_ENV_SECRET_PREFIXES ):
377+ return True
378+ return any (marker in upper for marker in _BYPASS_ENV_SECRET_MARKERS )
379+
380+
381+ def _build_bypass_env (workdir : str ) -> dict [str , str ]:
382+ """Env for bypass exec: full host env (unrestricted) minus credential vars,
383+ with HOME/TMPDIR repointed at the workdir so SDKs cannot read cached creds.
384+ """
385+ env = {k : v for k , v in os .environ .items () if not _is_secret_env_name (k )}
386+ env ["HOME" ] = workdir
387+ env ["TMPDIR" ] = workdir
388+ return env
389+
390+
332391def _sandbox_preexec ():
333392 """Best-effort sandbox setup for sandboxed subprocesses.
334393
@@ -403,6 +462,18 @@ def _sandbox_preexec():
403462 pass
404463
405464
465+ def _bypass_preexec ():
466+ """Minimal pre-exec for bypass exec: os.setsid() only.
467+
468+ Required, not a restriction: _kill_process_tree does killpg(getpgid(child)),
469+ so without a new session a timeout/cancel would kill the Studio server too.
470+ """
471+ try :
472+ os .setsid ()
473+ except OSError :
474+ pass
475+
476+
406477def _get_shell_cmd (command : str ) -> list [str ]:
407478 """Return the platform-appropriate shell invocation for a command string."""
408479 if sys .platform == "win32" :
@@ -609,12 +680,16 @@ def execute_tool(
609680 cancel_event = None ,
610681 timeout : int | None = _TIMEOUT_UNSET ,
611682 session_id : str | None = None ,
683+ disable_sandbox : bool = False ,
612684) -> str :
613685 """Execute a tool by name with the given arguments. Returns result as a string.
614686
615687 ``timeout``: int sets per-call limit in seconds, ``None`` means no limit,
616688 unset (default) uses ``_EXEC_TIMEOUT`` (300 s).
617689 ``session_id``: optional thread/session ID for per-conversation sandbox isolation.
690+ ``disable_sandbox``: Bypass Permissions; run python/terminal without the
691+ safety checks, blocklist, or resource caps (secrets still stripped). Only
692+ affects local code tools; web_search / MCP are unchanged.
618693 """
619694 logger .info (
620695 f"execute_tool: name={ name } , session_id={ session_id } , timeout={ timeout } "
@@ -647,11 +722,19 @@ def execute_tool(
647722 )
648723 if name == "python" :
649724 return _python_exec (
650- arguments .get ("code" , "" ), cancel_event , effective_timeout , session_id
725+ arguments .get ("code" , "" ),
726+ cancel_event ,
727+ effective_timeout ,
728+ session_id ,
729+ disable_sandbox = disable_sandbox ,
651730 )
652731 if name == "terminal" :
653732 return _bash_exec (
654- arguments .get ("command" , "" ), cancel_event , effective_timeout , session_id
733+ arguments .get ("command" , "" ),
734+ cancel_event ,
735+ effective_timeout ,
736+ session_id ,
737+ disable_sandbox = disable_sandbox ,
655738 )
656739 return f"Unknown tool: { name } "
657740
@@ -1987,15 +2070,21 @@ def _python_exec(
19872070 cancel_event = None ,
19882071 timeout : int = _EXEC_TIMEOUT ,
19892072 session_id : str | None = None ,
2073+ disable_sandbox : bool = False ,
19902074) -> str :
1991- """Execute Python code in a subprocess sandbox."""
2075+ """Execute Python code in a subprocess sandbox.
2076+
2077+ disable_sandbox (Bypass Permissions): skip the safety analysis and rlimit
2078+ pre-exec, and use the host env minus secrets.
2079+ """
19922080 if not code or not code .strip ():
19932081 return "No code provided."
19942082
1995- # Validate imports and code safety
1996- error = _check_code_safety (code )
1997- if error :
1998- return error
2083+ # Validate imports and code safety (skipped when the sandbox is disabled)
2084+ if not disable_sandbox :
2085+ error = _check_code_safety (code )
2086+ if error :
2087+ return error
19992088
20002089 tmp_path = None
20012090 workdir = _get_workdir (session_id )
@@ -2017,7 +2106,9 @@ def _python_exec(
20172106 with os .fdopen (fd , "w" ) as f :
20182107 f .write (code )
20192108
2020- safe_env = _build_safe_env (workdir )
2109+ safe_env = (
2110+ _build_bypass_env (workdir ) if disable_sandbox else _build_safe_env (workdir )
2111+ )
20212112 popen_kwargs = dict (
20222113 stdout = subprocess .PIPE ,
20232114 stderr = subprocess .STDOUT ,
@@ -2026,7 +2117,9 @@ def _python_exec(
20262117 env = safe_env ,
20272118 )
20282119 if sys .platform != "win32" :
2029- popen_kwargs ["preexec_fn" ] = _sandbox_preexec
2120+ popen_kwargs ["preexec_fn" ] = (
2121+ _bypass_preexec if disable_sandbox else _sandbox_preexec
2122+ )
20302123 else :
20312124 popen_kwargs ["creationflags" ] = subprocess .CREATE_NO_WINDOW
20322125
@@ -2094,19 +2187,27 @@ def _bash_exec(
20942187 cancel_event = None ,
20952188 timeout : int = _EXEC_TIMEOUT ,
20962189 session_id : str | None = None ,
2190+ disable_sandbox : bool = False ,
20972191) -> str :
2098- """Execute a bash command in a subprocess sandbox."""
2192+ """Execute a bash command in a subprocess sandbox.
2193+
2194+ disable_sandbox (Bypass Permissions): skip the command blocklist and rlimit
2195+ pre-exec, and use the host env minus secrets.
2196+ """
20992197 if not command or not command .strip ():
21002198 return "No command provided."
21012199
2102- # Block dangerous commands (shlex + regex based)
2103- blocked = _find_blocked_commands (command )
2104- if blocked :
2105- return f"Blocked command(s) for safety: { ', ' .join (sorted (blocked ))} "
2200+ # Block dangerous commands (skipped when the sandbox is disabled)
2201+ if not disable_sandbox :
2202+ blocked = _find_blocked_commands (command )
2203+ if blocked :
2204+ return f"Blocked command(s) for safety: { ', ' .join (sorted (blocked ))} "
21062205
21072206 try :
21082207 workdir = _get_workdir (session_id )
2109- safe_env = _build_safe_env (workdir )
2208+ safe_env = (
2209+ _build_bypass_env (workdir ) if disable_sandbox else _build_safe_env (workdir )
2210+ )
21102211 popen_kwargs = dict (
21112212 stdout = subprocess .PIPE ,
21122213 stderr = subprocess .STDOUT ,
@@ -2115,7 +2216,9 @@ def _bash_exec(
21152216 env = safe_env ,
21162217 )
21172218 if sys .platform != "win32" :
2118- popen_kwargs ["preexec_fn" ] = _sandbox_preexec
2219+ popen_kwargs ["preexec_fn" ] = (
2220+ _bypass_preexec if disable_sandbox else _sandbox_preexec
2221+ )
21192222 else :
21202223 popen_kwargs ["creationflags" ] = subprocess .CREATE_NO_WINDOW
21212224
0 commit comments