@@ -389,8 +389,15 @@ impl ToolExecutor {
389389 sh_process
390390 } ;
391391
392+ let safe_path = std:: env:: var ( "PATH" )
393+ . unwrap_or_else ( |_| "/usr/local/bin:/usr/bin:/bin" . to_string ( ) ) ;
394+
392395 command
393396 . current_dir ( & self . sandbox )
397+ . env_clear ( )
398+ . env ( "PATH" , safe_path)
399+ . env ( "LANG" , std:: env:: var ( "LANG" ) . unwrap_or_else ( |_| "en_US.UTF-8" . to_string ( ) ) )
400+ . env ( "LC_ALL" , std:: env:: var ( "LC_ALL" ) . unwrap_or_else ( |_| "en_US.UTF-8" . to_string ( ) ) )
394401 . stdout ( Stdio :: piped ( ) )
395402 . stderr ( Stdio :: piped ( ) )
396403 . kill_on_drop ( true ) ;
@@ -958,6 +965,30 @@ mod tests {
958965 cleanup ( "run_cmd_invalid" ) ;
959966 }
960967
968+ #[ tokio:: test]
969+ async fn test_run_command_env_isolation ( ) {
970+ let sandbox_path = with_sandbox ( "run_cmd_env_isolation" ) ;
971+
972+ std:: env:: set_var ( "ENOWX_SECRET_TEST_VAR" , "super_secret_value" ) ;
973+
974+ let executor = ToolExecutor :: new ( sandbox_path) ;
975+
976+ let call = ToolCall {
977+ tool : ToolName :: RunCommand ,
978+ input : serde_json:: json!( { "command" : "echo ${ENOWX_SECRET_TEST_VAR:-empty}" } ) ,
979+ } ;
980+ let result = executor. execute ( call) . await ;
981+ assert ! ( !result. is_error, "command should succeed: {}" , result. output) ;
982+ assert ! (
983+ !result. output. contains( "super_secret_value" ) ,
984+ "parent env vars must not leak into agent commands, got: {}" ,
985+ result. output
986+ ) ;
987+
988+ std:: env:: remove_var ( "ENOWX_SECRET_TEST_VAR" ) ;
989+ cleanup ( "run_cmd_env_isolation" ) ;
990+ }
991+
961992 #[ tokio:: test]
962993 async fn test_run_command_timeout ( ) {
963994 let sandbox_path = with_sandbox ( "run_cmd_timeout" ) ;
0 commit comments