diff --git a/projects/README.md b/projects/README.md index 1f32ec326..c448da742 100644 --- a/projects/README.md +++ b/projects/README.md @@ -3,14 +3,16 @@ 该目录为 FastGPT 主项目。 - app fastgpt 核心应用。 -- sandbox 沙盒项目,用于运行工作流里的代码执行 (需求python环境为python:3.11,额外安装的包请于requirements.txt填写,同时注意个别包可能额外安装库(如pandas需要安装libffi))。 - - 新加入python包遇见超时或者权限拦截的问题(确定不是自己的语法问题),请进入docker容器内部执行以下指令: +- sandbox 沙盒项目,用于运行工作流里的代码执行 (需求python环境为python:3.11,额外安装的包请于requirements.txt填写,在运行时会读取安装。 + + - 注意个别安装的包可能需要额外安装库(如pandas需要安装libffi))。 + + - 新加入python的包遇见超时或者权限拦截的问题(确定不是自己的语法问题),请进入docker容器内部执行以下指令: ```shell docker exec -it 《替换成容器名》 /bin/bash chmod -x testSystemCall.sh bash ./testSystemCall.sh ``` - - 然后将新的数组替换src下sandbox的constants.py中的SYSTEM_CALLS数组即可 + 然后将新的数组替换或追加到src下sandbox的constants.py中的SYSTEM_CALLS数组即可 diff --git a/projects/sandbox/src/sandbox/constants.ts b/projects/sandbox/src/sandbox/constants.ts index 604b50aa9..84283467e 100644 --- a/projects/sandbox/src/sandbox/constants.ts +++ b/projects/sandbox/src/sandbox/constants.ts @@ -1,4 +1,5 @@ export const pythonScript = ` +import os import subprocess import json import ast @@ -20,6 +21,7 @@ def extract_imports(code): seccomp_prefix = """ from seccomp import * import sys +import errno allowed_syscalls = [ "syscall.SYS_ARCH_PRCTL", "syscall.SYS_BRK", "syscall.SYS_CLONE", "syscall.SYS_CLOSE", "syscall.SYS_EPOLL_CREATE1", "syscall.SYS_EXECVE", @@ -99,22 +101,22 @@ def run_pythonCode(data:dict): variables = data["variables"] imports = "\\n".join(extract_imports(code)) var_def = "" - output_code = "res = main(" + output_code = "if __name__ == '__main__':\\n res = main(" for k, v in variables.items(): - if isinstance(v, str): - one_var = k + " = \\"" + v + "\\"\\n" - else: - one_var = k + " = " + str(v) + "\\n" + one_var = f"{k} = {json.dumps(v)}\\n" var_def = var_def + one_var output_code = output_code + k + ", " if output_code[-1] == "(": output_code = output_code + ")\\n" else: output_code = output_code[:-2] + ")\\n" - output_code = output_code + "print(res)" + output_code = output_code + " print(res)" code = imports + "\\n" + seccomp_prefix + "\\n" + var_def + "\\n" + code + "\\n" + output_code + tmp_file = os.path.join(data["tempDir"], "subProcess.py") + with open(tmp_file, "w", encoding="utf-8") as f: + f.write(code) try: - result = subprocess.run(["python3", "-c", code], capture_output=True, text=True, timeout=10) + result = subprocess.run(["python3", tmp_file], capture_output=True, text=True, timeout=10) if result.returncode == -31: return {"error": "Dangerous behavior detected."} if result.stderr != "": diff --git a/projects/sandbox/src/sandbox/utils.ts b/projects/sandbox/src/sandbox/utils.ts index d82bf49de..be6733aed 100644 --- a/projects/sandbox/src/sandbox/utils.ts +++ b/projects/sandbox/src/sandbox/utils.ts @@ -1,6 +1,9 @@ import { RunCodeDto, RunCodeResponse } from 'src/sandbox/dto/create-sandbox.dto'; import IsolatedVM, { ExternalCopy, Isolate, Reference } from 'isolated-vm'; - +import { mkdtemp, writeFile } from 'fs/promises'; +import { tmpdir } from 'os'; +import { join } from 'path'; +import { rmSync } from 'fs'; import { countToken } from './jsFn/tiktoken'; import { timeDelay } from './jsFn/delay'; import { strToBase64 } from './jsFn/str2Base64'; @@ -9,7 +12,7 @@ import { createHmac } from './jsFn/crypto'; import { spawn } from 'child_process'; import { pythonScript } from './constants'; const CustomLogStr = 'CUSTOM_LOG'; - +const PythonScriptFileName = 'main.py'; export const runJsSandbox = async ({ code, variables = {} @@ -112,15 +115,16 @@ export const runPythonSandbox = async ({ code, variables = {} }: RunCodeDto): Promise => { + const tempDir = await mkdtemp(join(tmpdir(), 'python_script_tmp_')); const mainCallCode = ` -data = ${JSON.stringify({ code, variables })} +data = ${JSON.stringify({ code, variables, tempDir })} res = run_pythonCode(data) print(json.dumps(res)) `; const fullCode = [pythonScript, mainCallCode].filter(Boolean).join('\n'); - - const pythonProcess = spawn('python3', ['-u', '-c', fullCode]); + const { path: tempFilePath, cleanup } = await createTempFile(tempDir, fullCode); + const pythonProcess = spawn('python3', ['-u', tempFilePath]); const stdoutChunks: string[] = []; const stderrChunks: string[] = []; @@ -137,7 +141,9 @@ print(json.dumps(res)) } }); }); - const stdout = await stdoutPromise; + const stdout = await stdoutPromise.finally(() => { + cleanup(); + }); try { const parsedOutput = JSON.parse(stdout); @@ -146,7 +152,10 @@ print(json.dumps(res)) } return { codeReturn: parsedOutput, log: '' }; } catch (err) { - if (stdout.includes('malformed node or string on line 1')) { + if ( + stdout.includes('malformed node or string on line 1') || + stdout.includes('invalid syntax (, line 1)') + ) { return Promise.reject(`The result should be a parsable variable, such as a list. ${stdout}`); } else if (stdout.includes('Unexpected end of JSON input')) { return Promise.reject(`Not allowed print or ${stdout}`); @@ -154,3 +163,24 @@ print(json.dumps(res)) return Promise.reject(`Run failed: ${err}`); } }; + +// write full code into a tmp file +async function createTempFile(tempFileDirPath: string, context: string) { + const tempFilePath = join(tempFileDirPath, PythonScriptFileName); + + try { + await writeFile(tempFilePath, context); + return { + path: tempFilePath, + cleanup: () => { + rmSync(tempFilePath); + rmSync(tempFileDirPath, { + recursive: true, + force: true + }); + } + }; + } catch (err) { + return Promise.reject(`write file err: ${err}`); + } +}