Skip to content

Commit 8bfce62

Browse files
refactor: avoid using temp files in sandbox.
1 parent 6d38a71 commit 8bfce62

File tree

4 files changed

+35
-63
lines changed

4 files changed

+35
-63
lines changed

apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -200,14 +200,6 @@ def execute(self, message_list: List[BaseMessage],
200200
mcp_enable, mcp_tool_ids, mcp_servers, mcp_source, tool_enable, tool_ids, mcp_output_enable)
201201

202202
def get_details(self, manage, **kwargs):
203-
# 删除临时生成的MCP代码文件
204-
if self.context.get('execute_ids'):
205-
executor = ToolExecutor(CONFIG.get('SANDBOX'))
206-
# 清理工具代码文件,延时删除,避免文件被占用
207-
for tool_id in self.context.get('execute_ids'):
208-
code_path = f'{executor.sandbox_path}/execute/{tool_id}.py'
209-
if os.path.exists(code_path):
210-
os.remove(code_path)
211203
return {
212204
'step_type': 'chat_step',
213205
'run_time': self.context['run_time'],
@@ -254,7 +246,6 @@ def _handle_mcp_request(self, mcp_enable, tool_enable, mcp_source, mcp_servers,
254246
if tool_enable:
255247
if tool_ids and len(tool_ids) > 0: # 如果有工具ID,则将其转换为MCP
256248
self.context['tool_ids'] = tool_ids
257-
self.context['execute_ids'] = []
258249
for tool_id in tool_ids:
259250
tool = QuerySet(Tool).filter(id=tool_id).first()
260251
if tool is None or tool.is_active is False:
@@ -264,9 +255,8 @@ def _handle_mcp_request(self, mcp_enable, tool_enable, mcp_source, mcp_servers,
264255
params = json.loads(rsa_long_decrypt(tool.init_params))
265256
else:
266257
params = {}
267-
_id, tool_config = executor.get_tool_mcp_config(tool.code, params)
258+
tool_config = executor.get_tool_mcp_config(tool.code, params)
268259

269-
self.context['execute_ids'].append(_id)
270260
mcp_servers_config[str(tool.id)] = tool_config
271261

272262
if len(mcp_servers_config) > 0:

apps/application/flow/step_node/ai_chat_step_node/impl/base_chat_node.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,6 @@ def _handle_mcp_request(self, mcp_enable, tool_enable, mcp_source, mcp_servers,
233233
if tool_enable:
234234
if tool_ids and len(tool_ids) > 0: # 如果有工具ID,则将其转换为MCP
235235
self.context['tool_ids'] = tool_ids
236-
self.context['execute_ids'] = []
237236
for tool_id in tool_ids:
238237
tool = QuerySet(Tool).filter(id=tool_id).first()
239238
if not tool.is_active:
@@ -243,9 +242,8 @@ def _handle_mcp_request(self, mcp_enable, tool_enable, mcp_source, mcp_servers,
243242
params = json.loads(rsa_long_decrypt(tool.init_params))
244243
else:
245244
params = {}
246-
_id, tool_config = executor.get_tool_mcp_config(tool.code, params)
245+
tool_config = executor.get_tool_mcp_config(tool.code, params)
247246

248-
self.context['execute_ids'].append(_id)
249247
mcp_servers_config[str(tool.id)] = tool_config
250248

251249
if len(mcp_servers_config) > 0:
@@ -307,14 +305,6 @@ def reset_message_list(message_list: List[BaseMessage], answer_text):
307305
return result
308306

309307
def get_details(self, index: int, **kwargs):
310-
# 删除临时生成的MCP代码文件
311-
if self.context.get('execute_ids'):
312-
executor = ToolExecutor(CONFIG.get('SANDBOX'))
313-
# 清理工具代码文件,延时删除,避免文件被占用
314-
for tool_id in self.context.get('execute_ids'):
315-
code_path = f'{executor.sandbox_path}/execute/{tool_id}.py'
316-
if os.path.exists(code_path):
317-
os.remove(code_path)
318308
return {
319309
'name': self.node.properties.get('stepName'),
320310
"index": index,

apps/common/utils/tool_code.py

Lines changed: 32 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
11
# coding=utf-8
22
import ast
3+
import base64
4+
import gzip
35
import json
46
import os
7+
import socket
58
import subprocess
69
import sys
7-
from textwrap import dedent
8-
import socket
910
import uuid_utils.compat as uuid
11+
from common.utils.logger import maxkb_logger
1012
from django.utils.translation import gettext_lazy as _
1113
from maxkb.const import BASE_DIR, CONFIG
1214
from maxkb.const import PROJECT_DIR
13-
from common.utils.logger import maxkb_logger
14-
import threading
15+
from textwrap import dedent
1516

1617
python_directory = sys.executable
1718

18-
class ToolExecutor:
1919

20+
class ToolExecutor:
2021
_dir_initialized = False
21-
_lock = threading.Lock()
22+
2223
def __init__(self, sandbox=False):
2324
self.sandbox = sandbox
2425
if sandbox:
@@ -29,29 +30,22 @@ def __init__(self, sandbox=False):
2930
self.user = None
3031
self.banned_keywords = CONFIG.get("SANDBOX_PYTHON_BANNED_KEYWORDS", 'nothing_is_banned').split(',');
3132
self.sandbox_so_path = f'{self.sandbox_path}/sandbox.so'
32-
with ToolExecutor._lock:
33-
self._init_dir()
33+
self._init_dir()
3434

3535
def _init_dir(self):
3636
if ToolExecutor._dir_initialized:
3737
# 只初始化一次
3838
return
39-
execute_file_path = os.path.join(self.sandbox_path, 'execute')
40-
os.makedirs(execute_file_path, 0o500, exist_ok=True)
41-
result_file_path = os.path.join(self.sandbox_path, 'result')
42-
os.makedirs(result_file_path, 0o300, exist_ok=True)
4339
if self.sandbox:
4440
os.system(f"chown {self.user}:root {self.sandbox_path}")
45-
os.system(f"chown -R {self.user}:root {execute_file_path}")
46-
os.system(f"chown -R {self.user}:root {result_file_path}")
4741
os.chmod(self.sandbox_path, 0o550)
4842
if CONFIG.get("SANDBOX_TMP_DIR_ENABLED", '0') == "1":
4943
tmp_dir_path = os.path.join(self.sandbox_path, 'tmp')
5044
os.makedirs(tmp_dir_path, 0o700, exist_ok=True)
5145
os.system(f"chown -R {self.user}:root {tmp_dir_path}")
46+
if os.path.exists(self.sandbox_so_path):
47+
os.chmod(self.sandbox_so_path, 0o440)
5248
try:
53-
if os.path.exists(self.sandbox_so_path):
54-
os.chmod(self.sandbox_so_path, 0o440)
5549
# 初始化host黑名单
5650
banned_hosts_file_path = f'{self.sandbox_path}/.SANDBOX_BANNED_HOSTS'
5751
if os.path.exists(banned_hosts_file_path):
@@ -74,13 +68,13 @@ def exec_code(self, code_str, keywords):
7468
_id = str(uuid.uuid7())
7569
success = '{"code":200,"msg":"成功","data":exec_result}'
7670
err = '{"code":500,"msg":str(e),"data":None}'
77-
result_path = f'{self.sandbox_path}/result/{_id}.result'
7871
python_paths = CONFIG.get_sandbox_python_package_paths().split(',')
7972
_exec_code = f"""
8073
try:
8174
import os
8275
import sys
8376
import json
77+
import base64
8478
path_to_exclude = ['/opt/py3/lib/python3.11/site-packages', '/opt/maxkb-app/apps']
8579
sys.path = [p for p in sys.path if p not in path_to_exclude]
8680
sys.path += {python_paths}
@@ -92,21 +86,21 @@ def exec_code(self, code_str, keywords):
9286
for local in locals_v:
9387
globals_v[local] = locals_v[local]
9488
exec_result=f(**keywords)
95-
with open({result_path!a}, 'w') as file:
96-
file.write(json.dumps({success}, default=str))
89+
print(f"{_id}:" + base64.b64encode(json.dumps({success}, default=str).encode()).decode())
9790
except Exception as e:
98-
with open({result_path!a}, 'w') as file:
99-
file.write(json.dumps({err}))
91+
print(f"{_id}:" + base64.b64encode(json.dumps({err}, default=str).encode()).decode())
10092
"""
10193
if self.sandbox:
102-
subprocess_result = self._exec_sandbox(_exec_code, _id)
94+
subprocess_result = self._exec_sandbox(_exec_code)
10395
else:
10496
subprocess_result = self._exec(_exec_code)
10597
if subprocess_result.returncode == 1:
10698
raise Exception(subprocess_result.stderr)
107-
with open(result_path, 'r') as file:
108-
result = json.loads(file.read())
109-
os.remove(result_path)
99+
lines = subprocess_result.stdout.splitlines()
100+
result_line = [line for line in lines if line.startswith(_id)]
101+
if not result_line:
102+
raise Exception("No result found.")
103+
result = json.loads(base64.b64decode(result_line[0].split(":", 1)[1]).decode())
110104
if result.get('code') == 200:
111105
return result.get('data')
112106
raise Exception(result.get('msg'))
@@ -194,18 +188,16 @@ def generate_mcp_server_code(self, code_str, params):
194188
"""
195189

196190
def get_tool_mcp_config(self, code, params):
197-
code = self.generate_mcp_server_code(code, params)
198-
199-
_id = uuid.uuid7()
200-
code_path = f'{self.sandbox_path}/execute/{_id}.py'
201-
with open(code_path, 'w') as f:
202-
f.write(code)
191+
_code = self.generate_mcp_server_code(code, params)
192+
maxkb_logger.debug(f"Python code of mcp tool: {_code}")
193+
compressed_and_base64_encoded_code_str = base64.b64encode(gzip.compress(_code.encode())).decode()
203194
if self.sandbox:
204195
tool_config = {
205196
'command': 'su',
206197
'args': [
207198
'-s', sys.executable,
208-
'-c', f"exec(open('{code_path}', 'r').read())",
199+
'-c',
200+
f'import base64,gzip; exec(gzip.decompress(base64.b64decode(\'{compressed_and_base64_encoded_code_str}\')).decode())',
209201
self.user,
210202
],
211203
'cwd': self.sandbox_path,
@@ -217,24 +209,24 @@ def get_tool_mcp_config(self, code, params):
217209
else:
218210
tool_config = {
219211
'command': sys.executable,
220-
'args': [code_path],
212+
'args': f'import base64,gzip; exec(gzip.decompress(base64.b64decode(\'{compressed_and_base64_encoded_code_str}\')).decode())',
221213
'transport': 'stdio',
222214
}
223-
return _id, tool_config
215+
return tool_config
224216

225-
def _exec_sandbox(self, _code, _id):
226-
exec_python_file = f'{self.sandbox_path}/execute/{_id}.py'
227-
with open(exec_python_file, 'w') as file:
228-
file.write(_code)
217+
def _exec_sandbox(self, _code):
229218
kwargs = {'cwd': BASE_DIR}
230219
kwargs['env'] = {
231220
'LD_PRELOAD': self.sandbox_so_path,
232221
}
222+
maxkb_logger.debug(f"Sandbox execute code: {_code}")
223+
compressed_and_base64_encoded_code_str = base64.b64encode(gzip.compress(_code.encode())).decode()
233224
subprocess_result = subprocess.run(
234-
['su', '-s', python_directory, '-c', "exec(open('" + exec_python_file + "').read())", self.user],
225+
['su', '-s', python_directory, '-c',
226+
f'import base64,gzip; exec(gzip.decompress(base64.b64decode(\'{compressed_and_base64_encoded_code_str}\')).decode())',
227+
self.user],
235228
text=True,
236229
capture_output=True, **kwargs)
237-
os.remove(exec_python_file)
238230
return subprocess_result
239231

240232
def validate_banned_keywords(self, code_str):

installer/start-maxkb.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
if [ ! -d /opt/maxkb/logs ]; then
44
mkdir -p /opt/maxkb/logs
5-
chmod 700 /opt/maxkb/logs
65
fi
6+
chmod -R 700 /opt/maxkb/logs
77
if [ ! -d /opt/maxkb/local ]; then
88
mkdir -p /opt/maxkb/local
99
chmod 700 /opt/maxkb/local

0 commit comments

Comments
 (0)