1010import tempfile
1111import pwd
1212import resource
13+ import getpass
1314import uuid_utils .compat as uuid
1415from common .utils .logger import maxkb_logger
1516from django .utils .translation import gettext_lazy as _
1617from maxkb .const import BASE_DIR , CONFIG
1718from maxkb .const import PROJECT_DIR
1819from textwrap import dedent
1920
20- python_directory = sys .executable
21-
22-
2321class ToolExecutor :
2422
23+ enable_sandbox = bool (CONFIG .get ('SANDBOX' , 0 ))
24+ sandbox_path = CONFIG .get ("SANDBOX_HOME" , '/opt/maxkb-app/sandbox' ) if enable_sandbox else os .path .join (PROJECT_DIR , 'data' , 'sandbox' )
25+ process_timeout_seconds = int (CONFIG .get ("SANDBOX_PYTHON_PROCESS_TIMEOUT_SECONDS" , '3600' ))
26+ process_limit_mem_mb = int (CONFIG .get ("SANDBOX_PYTHON_PROCESS_LIMIT_MEM_MB" , '256' ))
27+
2528 def __init__ (self , sandbox = False ):
2629 self .sandbox = sandbox
2730 if sandbox :
28- self .sandbox_path = CONFIG .get ("SANDBOX_HOME" , '/opt/maxkb-app/sandbox' )
2931 self .user = 'sandbox'
3032 else :
31- self .sandbox_path = os .path .join (PROJECT_DIR , 'data' , 'sandbox' )
32- self .user = None
33- self .sandbox_so_path = f'{ self .sandbox_path } /lib/sandbox.so'
34- self .process_timeout_seconds = int (CONFIG .get ("SANDBOX_PYTHON_PROCESS_TIMEOUT_SECONDS" , '3600' ))
35- self .process_limit_mem_mb = int (CONFIG .get ("SANDBOX_PYTHON_PROCESS_LIMIT_MEM_MB" , '256' ))
36- try :
37- self ._init_sandbox_dir ()
38- except Exception as e :
39- # 本机忽略异常,容器内不忽略
40- maxkb_logger .error (f'Exception: { e } ' , exc_info = True )
41- if self .sandbox :
42- raise e
43-
44- def _init_sandbox_dir (self ):
45- if not self .sandbox :
46- # 不是sandbox就不初始化目录
33+ self .user = getpass .getuser ()
34+
35+ @staticmethod
36+ def init_sandbox_dir ():
37+ if not ToolExecutor .enable_sandbox :
38+ # 不启用sandbox就不初始化目录
4739 return
4840 try :
4941 # 只初始化一次
@@ -63,7 +55,7 @@ def _init_sandbox_dir(self):
6355 if CONFIG .get ("SANDBOX_TMP_DIR_ENABLED" , '0' ) == "1" :
6456 os .system ("chmod g+rwx /tmp" )
6557 # 初始化sandbox配置文件
66- sandbox_lib_path = os .path .dirname (self . sandbox_so_path )
58+ sandbox_lib_path = os .path .dirname (f' { ToolExecutor . sandbox_path } /lib/sandbox.so' )
6759 sandbox_conf_file_path = f'{ sandbox_lib_path } /.sandbox.conf'
6860 if os .path .exists (sandbox_conf_file_path ):
6961 os .remove (sandbox_conf_file_path )
@@ -76,7 +68,8 @@ def _init_sandbox_dir(self):
7668 with open (sandbox_conf_file_path , "w" ) as f :
7769 f .write (f"SANDBOX_PYTHON_BANNED_HOSTS={ banned_hosts } \n " )
7870 f .write (f"SANDBOX_PYTHON_ALLOW_SUBPROCESS={ allow_subprocess } \n " )
79- os .system (f"chmod -R 550 { self .sandbox_path } " )
71+ os .system (f"chmod -R 550 { ToolExecutor .sandbox_path } " )
72+
8073
8174 def exec_code (self , code_str , keywords , function_name = None ):
8275 _id = str (uuid .uuid7 ())
@@ -103,7 +96,7 @@ def exec_code(self, code_str, keywords, function_name=None):
10396 exec_result=f(**keywords)
10497 builtins.print("\\ n{ _id } :"+base64.b64encode(json.dumps({ success } , default=str).encode()).decode(), flush=True)
10598except Exception as e:
106- if isinstance(e, MemoryError): e = Exception("Cannot allocate more memory: exceeded the limit of { self .process_limit_mem_mb } MB.")
99+ if isinstance(e, MemoryError): e = Exception("Cannot allocate more memory: exceeded the limit of { ToolExecutor .process_limit_mem_mb } MB.")
107100 builtins.print("\\ n{ _id } :"+base64.b64encode(json.dumps({ err } , default=str).encode()).decode(), flush=True)
108101"""
109102 maxkb_logger .debug (f"Sandbox execute code: { _exec_code } " )
@@ -213,37 +206,38 @@ def get_tool_mcp_config(self, code, params):
213206 '-c' ,
214207 f'import base64,gzip; exec(gzip.decompress(base64.b64decode(\' { compressed_and_base64_encoded_code_str } \' )).decode())' ,
215208 ],
216- 'cwd' : self .sandbox_path ,
209+ 'cwd' : ToolExecutor .sandbox_path ,
217210 'env' : {
218- 'LD_PRELOAD' : self . sandbox_so_path ,
211+ 'LD_PRELOAD' : f' { ToolExecutor . sandbox_path } /lib/sandbox.so' ,
219212 },
220213 'transport' : 'stdio' ,
221214 }
222215 return tool_config
223216
224217 def _exec (self , execute_file ):
225218 kwargs = {'cwd' : BASE_DIR , 'env' : {
226- 'LD_PRELOAD' : self . sandbox_so_path ,
219+ 'LD_PRELOAD' : f' { ToolExecutor . sandbox_path } /lib/sandbox.so' ,
227220 }}
228221 try :
229- def set_resource_limit ():
230- if not self .sandbox :
231- return
232- mem_limit = self .process_limit_mem_mb * 1024 * 1024
233- resource .setrlimit (resource .RLIMIT_AS , (mem_limit , mem_limit ))
234222 subprocess_result = subprocess .run (
235- [python_directory , execute_file ],
236- preexec_fn = set_resource_limit ,
237- timeout = self .process_timeout_seconds ,
223+ [sys .executable , execute_file ],
224+ timeout = ToolExecutor .process_timeout_seconds ,
238225 text = True ,
239- capture_output = True , ** kwargs )
226+ capture_output = True ,
227+ ** kwargs ,
228+ preexec_fn = lambda : (None if not self .sandbox else resource .setrlimit (resource .RLIMIT_AS , (ToolExecutor .process_limit_mem_mb * 1024 * 1024 ,) * 2 ))
229+ )
240230 return subprocess_result
241231 except subprocess .TimeoutExpired :
242- raise Exception (_ (f"Process execution timed out after { self .process_timeout_seconds } seconds." ))
232+ raise Exception (_ (f"Process execution timed out after { ToolExecutor .process_timeout_seconds } seconds." ))
243233
244234 def validate_mcp_transport (self , code_str ):
245235 servers = json .loads (code_str )
246236 for server , config in servers .items ():
247237 if config .get ('transport' ) not in ['sse' , 'streamable_http' ]:
248238 raise Exception (_ ('Only support transport=sse or transport=streamable_http' ))
249239
240+ try :
241+ ToolExecutor .init_sandbox_dir ()
242+ except Exception as e :
243+ maxkb_logger .error (f'Exception: { e } ' , exc_info = True )
0 commit comments