1919from maxkb .const import PROJECT_DIR
2020from textwrap import dedent
2121
22- class ToolExecutor :
22+ _enable_sandbox = bool (CONFIG .get ('SANDBOX' , 0 ))
23+ _run_user = 'sandbox' if _enable_sandbox else getpass .getuser ()
24+ _sandbox_path = CONFIG .get ("SANDBOX_HOME" , '/opt/maxkb-app/sandbox' ) if _enable_sandbox else os .path .join (PROJECT_DIR , 'data' , 'sandbox' )
25+ _process_limit_timeout_seconds = int (CONFIG .get ("SANDBOX_PYTHON_PROCESS_LIMIT_TIMEOUT_SECONDS" , '3600' ))
26+ _process_limit_cpu_cores = min (max (int (CONFIG .get ("SANDBOX_PYTHON_PROCESS_LIMIT_CPU_CORES" , '1' )), 1 ), len (os .sched_getaffinity (0 ))) if sys .platform .startswith ("linux" ) else os .cpu_count () # 只支持linux,window和mac不支持
27+ _process_limit_mem_mb = int (CONFIG .get ("SANDBOX_PYTHON_PROCESS_LIMIT_MEM_MB" , '256' ))
2328
24- enable_sandbox = bool (CONFIG .get ('SANDBOX' , 0 ))
25- sandbox_path = CONFIG .get ("SANDBOX_HOME" , '/opt/maxkb-app/sandbox' ) if enable_sandbox else os .path .join (PROJECT_DIR , 'data' , 'sandbox' )
26- process_timeout_seconds = int (CONFIG .get ("SANDBOX_PYTHON_PROCESS_TIMEOUT_SECONDS" , '3600' ))
27- process_limit_mem_mb = int (CONFIG .get ("SANDBOX_PYTHON_PROCESS_LIMIT_MEM_MB" , '256' ))
28- process_limit_cpu_cores = min (max (int (CONFIG .get ("SANDBOX_PYTHON_PROCESS_LIMIT_CPU_CORES" , '1' )), 1 ), len (os .sched_getaffinity (0 ))) if sys .platform .startswith ("linux" ) else os .cpu_count () # 只支持linux,window和mac不支持
29+ class ToolExecutor :
2930
30- def __init__ (self , sandbox = False ):
31- self .sandbox = sandbox
32- if sandbox :
33- self .user = 'sandbox'
34- else :
35- self .user = getpass .getuser ()
31+ def __init__ (self ):
32+ pass
3633
3734 @staticmethod
3835 def init_sandbox_dir ():
39- if not ToolExecutor . enable_sandbox :
36+ if not _enable_sandbox :
4037 # 不启用sandbox就不初始化目录
4138 return
4239 try :
@@ -57,7 +54,7 @@ def init_sandbox_dir():
5754 if CONFIG .get ("SANDBOX_TMP_DIR_ENABLED" , '0' ) == "1" :
5855 os .system ("chmod g+rwx /tmp" )
5956 # 初始化sandbox配置文件
60- sandbox_lib_path = os .path .dirname (f'{ ToolExecutor . sandbox_path } /lib/sandbox.so' )
57+ sandbox_lib_path = os .path .dirname (f'{ _sandbox_path } /lib/sandbox.so' )
6158 sandbox_conf_file_path = f'{ sandbox_lib_path } /.sandbox.conf'
6259 if os .path .exists (sandbox_conf_file_path ):
6360 os .remove (sandbox_conf_file_path )
@@ -70,16 +67,20 @@ def init_sandbox_dir():
7067 with open (sandbox_conf_file_path , "w" ) as f :
7168 f .write (f"SANDBOX_PYTHON_BANNED_HOSTS={ banned_hosts } \n " )
7269 f .write (f"SANDBOX_PYTHON_ALLOW_SUBPROCESS={ allow_subprocess } \n " )
73- os .system (f"chmod -R 550 { ToolExecutor . sandbox_path } " )
70+ os .system (f"chmod -R 550 { _sandbox_path } " )
7471
72+ try :
73+ init_sandbox_dir ()
74+ except Exception as e :
75+ maxkb_logger .error (f'Exception: { e } ' , exc_info = True )
7576
7677 def exec_code (self , code_str , keywords , function_name = None ):
7778 _id = str (uuid .uuid7 ())
7879 success = '{"code":200,"msg":"成功","data":exec_result}'
7980 err = '{"code":500,"msg":str(e),"data":None}'
8081 action_function = f'({ function_name !a} , locals_v.get({ function_name !a} ))' if function_name else 'locals_v.popitem()'
8182 python_paths = CONFIG .get_sandbox_python_package_paths ().split (',' )
82- set_run_user = f'os.setgid({ pwd .getpwnam (self . user ).pw_gid } );os.setuid({ pwd .getpwnam (self . user ).pw_uid } );' if self . sandbox else ''
83+ set_run_user = f'os.setgid({ pwd .getpwnam (_run_user ).pw_gid } );os.setuid({ pwd .getpwnam (_run_user ).pw_uid } );' if _enable_sandbox else ''
8384 _exec_code = f"""
8485try:
8586 import os, sys, json, base64, builtins
@@ -98,7 +99,7 @@ def exec_code(self, code_str, keywords, function_name=None):
9899 exec_result=f(**keywords)
99100 builtins.print("\\ n{ _id } :"+base64.b64encode(json.dumps({ success } , default=str).encode()).decode(), flush=True)
100101except Exception as e:
101- if isinstance(e, MemoryError): e = Exception("Cannot allocate more memory: exceeded the limit of { ToolExecutor . process_limit_mem_mb } MB.")
102+ if isinstance(e, MemoryError): e = Exception("Cannot allocate more memory: exceeded the limit of { _process_limit_mem_mb } MB.")
102103 builtins.print("\\ n{ _id } :"+base64.b64encode(json.dumps({ err } , default=str).encode()).decode(), flush=True)
103104"""
104105 maxkb_logger .debug (f"Sandbox execute code: { _exec_code } " )
@@ -184,7 +185,7 @@ def _generate_mcp_server_code(self, _code, params):
184185 def generate_mcp_server_code (self , code_str , params ):
185186 python_paths = CONFIG .get_sandbox_python_package_paths ().split (',' )
186187 code = self ._generate_mcp_server_code (code_str , params )
187- set_run_user = f'os.setgid({ pwd .getpwnam (self . user ).pw_gid } );os.setuid({ pwd .getpwnam (self . user ).pw_uid } );' if self . sandbox else ''
188+ set_run_user = f'os.setgid({ pwd .getpwnam (_run_user ).pw_gid } );os.setuid({ pwd .getpwnam (_run_user ).pw_uid } );' if _enable_sandbox else ''
188189 return f"""
189190import os, sys, logging
190191logging.basicConfig(level=logging.WARNING)
@@ -208,41 +209,36 @@ def get_tool_mcp_config(self, code, params):
208209 '-c' ,
209210 f'import base64,gzip; exec(gzip.decompress(base64.b64decode(\' { compressed_and_base64_encoded_code_str } \' )).decode())' ,
210211 ],
211- 'cwd' : ToolExecutor . sandbox_path ,
212+ 'cwd' : _sandbox_path ,
212213 'env' : {
213- 'LD_PRELOAD' : f'{ ToolExecutor . sandbox_path } /lib/sandbox.so' ,
214+ 'LD_PRELOAD' : f'{ _sandbox_path } /lib/sandbox.so' ,
214215 },
215216 'transport' : 'stdio' ,
216217 }
217218 return tool_config
218219
219220 def _exec (self , execute_file ):
220221 kwargs = {'cwd' : BASE_DIR , 'env' : {
221- 'LD_PRELOAD' : f'{ ToolExecutor . sandbox_path } /lib/sandbox.so' ,
222+ 'LD_PRELOAD' : f'{ _sandbox_path } /lib/sandbox.so' ,
222223 }}
223224 try :
224225 subprocess_result = subprocess .run (
225226 [sys .executable , execute_file ],
226- timeout = ToolExecutor . process_timeout_seconds ,
227+ timeout = _process_limit_timeout_seconds ,
227228 text = True ,
228229 capture_output = True ,
229230 ** kwargs ,
230- preexec_fn = (lambda : None if (not self . sandbox or not sys .platform .startswith ("linux" )) else (
231- resource .setrlimit (resource .RLIMIT_AS , (ToolExecutor . process_limit_mem_mb * 1024 * 1024 ,) * 2 ),
232- os .sched_setaffinity (0 , set (random .sample (list (os .sched_getaffinity (0 )), ToolExecutor . process_limit_cpu_cores )))
231+ preexec_fn = (lambda : None if (not _enable_sandbox or not sys .platform .startswith ("linux" )) else (
232+ resource .setrlimit (resource .RLIMIT_AS , (_process_limit_mem_mb * 1024 * 1024 ,) * 2 ),
233+ os .sched_setaffinity (0 , set (random .sample (list (os .sched_getaffinity (0 )), _process_limit_cpu_cores )))
233234 ))
234235 )
235236 return subprocess_result
236237 except subprocess .TimeoutExpired :
237- raise Exception (_ (f"Process execution timed out after { ToolExecutor . process_timeout_seconds } seconds." ))
238+ raise Exception (_ (f"Process execution timed out after { _process_limit_timeout_seconds } seconds." ))
238239
239240 def validate_mcp_transport (self , code_str ):
240241 servers = json .loads (code_str )
241242 for server , config in servers .items ():
242243 if config .get ('transport' ) not in ['sse' , 'streamable_http' ]:
243244 raise Exception (_ ('Only support transport=sse or transport=streamable_http' ))
244-
245- try :
246- ToolExecutor .init_sandbox_dir ()
247- except Exception as e :
248- maxkb_logger .error (f'Exception: { e } ' , exc_info = True )
0 commit comments