mirror of
https://github.com/labring/FastGPT.git
synced 2025-10-20 18:54:09 +00:00
V4.12.4 features (#5626)
* fix: push again, user select option button and form input radio content overflow (#5601) * fix: push again, user select option button and form input radio content overflow * fix: use useCallback instead of useMemo, fix unnecessary delete * fix: Move the variable inside the component * fix: do not pass valueLabel to MySelect * ui * del collection api adapt * refactor: inherit permission (#5529) * refactor: permission update conflict check function * refactor(permission): app collaborator update api * refactor(permission): support app update collaborator * feat: support fe permission conflict check * refactor(permission): app permission * refactor(permission): dataset permission * refactor(permission): team permission * chore: fe adjust * fix: type error * fix: audit pagiation * fix: tc * chore: initv4130 * fix: app/dataset auth logic * chore: move code * refactor(permission): remove selfPermission * fix: mock * fix: test * fix: app & dataset auth * fix: inherit * test(inheritPermission): test syncChildrenPermission * prompt editor add list plugin (#5620) * perf: search result (#5608) * fix: table size (#5598) * temp: list value * backspace * optimize code --------- Co-authored-by: Archer <545436317@qq.com> Co-authored-by: 伍闲犬 <whoeverimf5@gmail.com> * fix: fe & member list (#5619) * chore: initv4130 * fix: MemberItemCard * fix: MemberItemCard * chore: fe adjust & init script * perf: test code * doc * fix debug variables (#5617) * perf: search result (#5608) * fix: table size (#5598) * fix debug variables * fix --------- Co-authored-by: Archer <545436317@qq.com> Co-authored-by: 伍闲犬 <whoeverimf5@gmail.com> * perf: member ui * fix: inherit bug (#5624) * refactor(permission): remove getClbsWithInfo, which is useless * fix: app list privateApp * fix: get infos * perf(fe): remove delete icon when it is disable in MemberItemCard * fix: dataset private dataset * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Archer <545436317@qq.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * perf: auto coupon * chore: upgrade script & get infos avatar (#5625) * fix: get infos * chore: initv4130 * feat: support WecomRobot publish, and fix AesKey can not save bug (#5526) * feat: resolve conflicts * fix: add param 'show_publish_wecom' * feat: abstract out WecomCrypto type * doc: wecom robot document * fix: solve instability in AI output * doc: update some pictures * feat: remove functions from request.ts to chat.ts and toolCall.ts * doc: wecom robot doc update * fix * delete unused code * doc: update version and prompt * feat: remove wecom crypto, delete wecom code in workflow * feat: delete unused codes --------- Co-authored-by: heheer <zhiyu44@qq.com> * remove test * rename init shell * feat: collection page store * reload sandbox * pysandbox * remove log * chore: remove useless code (#5629) * chore: remove useless code * fix: checkConflict * perf: support hidden type for RoleList * fix: copy node * update doc * fix(permission): some bug (#5632) * fix: app/dataset list * fix: inherit bug * perf: del app;i18n;save chat * fix: test * i18n * fix: sumper overflow return OwnerRoleVal (#5633) * remove invalid code * fix: scroll * fix: objectId * update next * update package * object id * mock redis * feat: add redis append to resolve wecom stream response (#5643) * feat: resolve conflicts * fix: add param 'show_publish_wecom' * feat: abstract out WecomCrypto type * doc: wecom robot document * fix: solve instability in AI output * doc: update some pictures * feat: remove functions from request.ts to chat.ts and toolCall.ts * doc: wecom robot doc update * fix * delete unused code * doc: update version and prompt * feat: remove wecom crypto, delete wecom code in workflow * feat: delete unused codes * feat: add redis append method --------- Co-authored-by: heheer <zhiyu44@qq.com> * cache per * fix(test): init team sub when creating mocked user (#5646) * fix: button is not vertically centered (#5647) * doc * fix: gridFs objectId (#5649) --------- Co-authored-by: Zeng Qingwen <143274079+fishwww-ww@users.noreply.github.com> Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com> Co-authored-by: heheer <heheer@sealos.io> Co-authored-by: 伍闲犬 <whoeverimf5@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: heheer <zhiyu44@qq.com>
This commit is contained in:
@@ -18,65 +18,109 @@ def extract_imports(code):
|
||||
for alias in node.names:
|
||||
imports.append(f"from {module} import {alias.name}")
|
||||
return imports
|
||||
|
||||
seccomp_prefix = """
|
||||
from seccomp import *
|
||||
import platform
|
||||
import sys
|
||||
import errno
|
||||
allowed_syscalls = [
|
||||
"syscall.SYS_NEWFSTATAT",
|
||||
"syscall.SYS_LSEEK",
|
||||
"syscall.SYS_GETDENTS64",
|
||||
"syscall.SYS_CLOSE",
|
||||
"syscall.SYS_FUTEX",
|
||||
"syscall.SYS_MMAP",
|
||||
"syscall.SYS_BRK",
|
||||
"syscall.SYS_MPROTECT",
|
||||
"syscall.SYS_MUNMAP",
|
||||
"syscall.SYS_RT_SIGRETURN",
|
||||
"syscall.SYS_MREMAP",
|
||||
"syscall.SYS_SETUID",
|
||||
"syscall.SYS_SETGID",
|
||||
"syscall.SYS_GETUID",
|
||||
"syscall.SYS_GETPID",
|
||||
"syscall.SYS_GETPPID",
|
||||
"syscall.SYS_GETTID",
|
||||
"syscall.SYS_EXIT",
|
||||
"syscall.SYS_EXIT_GROUP",
|
||||
"syscall.SYS_TGKILL",
|
||||
"syscall.SYS_RT_SIGACTION",
|
||||
"syscall.SYS_SCHED_YIELD",
|
||||
"syscall.SYS_SET_ROBUST_LIST",
|
||||
"syscall.SYS_GET_ROBUST_LIST",
|
||||
"syscall.SYS_RSEQ",
|
||||
"syscall.SYS_CLOCK_GETTIME",
|
||||
"syscall.SYS_GETTIMEOFDAY",
|
||||
"syscall.SYS_NANOSLEEP",
|
||||
"syscall.SYS_CLOCK_NANOSLEEP",
|
||||
"syscall.SYS_TIME",
|
||||
"syscall.SYS_RT_SIGPROCMASK",
|
||||
"syscall.SYS_SIGALTSTACK",
|
||||
"syscall.SYS_CLONE",
|
||||
"syscall.SYS_MKDIRAT",
|
||||
"syscall.SYS_MKDIR",
|
||||
"syscall.SYS_FSTAT",
|
||||
"syscall.SYS_FCNTL",
|
||||
"syscall.SYS_FSTATFS",
|
||||
]
|
||||
allowed_syscalls_tmp = allowed_syscalls
|
||||
L = []
|
||||
for item in allowed_syscalls_tmp:
|
||||
item = item.strip()
|
||||
parts = item.split(".")[1][4:].lower()
|
||||
L.append(parts)
|
||||
f = SyscallFilter(defaction=KILL)
|
||||
for item in L:
|
||||
f.add_rule(ALLOW, item)
|
||||
f.add_rule(ALLOW, "write", Arg(0, EQ, sys.stdout.fileno()))
|
||||
f.add_rule(ALLOW, "write", Arg(0, EQ, sys.stderr.fileno()))
|
||||
f.add_rule(ALLOW, 307)
|
||||
f.add_rule(ALLOW, 318)
|
||||
f.add_rule(ALLOW, 334)
|
||||
f.load()
|
||||
|
||||
# Skip seccomp on macOS since it's Linux-specific
|
||||
if platform.system() == 'Linux':
|
||||
try:
|
||||
from seccomp import *
|
||||
import errno
|
||||
allowed_syscalls = [
|
||||
# File operations - READ ONLY (removed SYS_WRITE)
|
||||
"syscall.SYS_READ",
|
||||
# Removed "syscall.SYS_WRITE" - no general write access
|
||||
"syscall.SYS_OPEN", # Still needed for reading files
|
||||
"syscall.SYS_OPENAT", # Still needed for reading files
|
||||
"syscall.SYS_CLOSE",
|
||||
"syscall.SYS_FSTAT",
|
||||
"syscall.SYS_LSTAT",
|
||||
"syscall.SYS_STAT",
|
||||
"syscall.SYS_NEWFSTATAT",
|
||||
"syscall.SYS_LSEEK",
|
||||
"syscall.SYS_GETDENTS64",
|
||||
"syscall.SYS_FCNTL",
|
||||
"syscall.SYS_ACCESS",
|
||||
"syscall.SYS_FACCESSAT",
|
||||
|
||||
# Memory management - essential for Python
|
||||
"syscall.SYS_MMAP",
|
||||
"syscall.SYS_BRK",
|
||||
"syscall.SYS_MPROTECT",
|
||||
"syscall.SYS_MUNMAP",
|
||||
"syscall.SYS_MREMAP",
|
||||
|
||||
# Process/thread operations
|
||||
"syscall.SYS_GETUID",
|
||||
"syscall.SYS_GETGID",
|
||||
"syscall.SYS_GETEUID",
|
||||
"syscall.SYS_GETEGID",
|
||||
"syscall.SYS_GETPID",
|
||||
"syscall.SYS_GETPPID",
|
||||
"syscall.SYS_GETTID",
|
||||
"syscall.SYS_EXIT",
|
||||
"syscall.SYS_EXIT_GROUP",
|
||||
|
||||
# Signal handling
|
||||
"syscall.SYS_RT_SIGACTION",
|
||||
"syscall.SYS_RT_SIGPROCMASK",
|
||||
"syscall.SYS_RT_SIGRETURN",
|
||||
"syscall.SYS_SIGALTSTACK",
|
||||
|
||||
# Time operations
|
||||
"syscall.SYS_CLOCK_GETTIME",
|
||||
"syscall.SYS_GETTIMEOFDAY",
|
||||
"syscall.SYS_TIME",
|
||||
|
||||
# Threading/synchronization
|
||||
"syscall.SYS_FUTEX",
|
||||
"syscall.SYS_SET_ROBUST_LIST",
|
||||
"syscall.SYS_GET_ROBUST_LIST",
|
||||
"syscall.SYS_CLONE",
|
||||
|
||||
# System info
|
||||
"syscall.SYS_UNAME",
|
||||
"syscall.SYS_ARCH_PRCTL",
|
||||
"syscall.SYS_RSEQ",
|
||||
|
||||
# I/O operations
|
||||
"syscall.SYS_IOCTL",
|
||||
"syscall.SYS_POLL",
|
||||
"syscall.SYS_SELECT",
|
||||
"syscall.SYS_PSELECT6",
|
||||
|
||||
# Process scheduling
|
||||
"syscall.SYS_SCHED_YIELD",
|
||||
"syscall.SYS_SCHED_GETAFFINITY",
|
||||
|
||||
# Additional Python runtime essentials
|
||||
"syscall.SYS_GETRANDOM",
|
||||
"syscall.SYS_GETCWD",
|
||||
"syscall.SYS_READLINK",
|
||||
"syscall.SYS_READLINKAT",
|
||||
]
|
||||
allowed_syscalls_tmp = allowed_syscalls
|
||||
L = []
|
||||
for item in allowed_syscalls_tmp:
|
||||
item = item.strip()
|
||||
parts = item.split(".")[1][4:].lower()
|
||||
L.append(parts)
|
||||
f = SyscallFilter(defaction=KILL)
|
||||
for item in L:
|
||||
f.add_rule(ALLOW, item)
|
||||
# Only allow writing to stdout and stderr for output
|
||||
f.add_rule(ALLOW, "write", Arg(0, EQ, sys.stdout.fileno()))
|
||||
f.add_rule(ALLOW, "write", Arg(0, EQ, sys.stderr.fileno()))
|
||||
# Remove other write-related syscalls
|
||||
# f.add_rule(ALLOW, 307) # Removed - might be file creation
|
||||
# f.add_rule(ALLOW, 318) # Removed - might be file creation
|
||||
# f.add_rule(ALLOW, 334) # Removed - might be file creation
|
||||
f.load()
|
||||
except ImportError:
|
||||
# seccomp module not available, skip security restrictions
|
||||
pass
|
||||
"""
|
||||
|
||||
def remove_print_statements(code):
|
||||
@@ -96,7 +140,13 @@ def remove_print_statements(code):
|
||||
return ast.unparse(modified_tree)
|
||||
|
||||
def detect_dangerous_imports(code):
|
||||
dangerous_modules = ["os", "sys", "subprocess", "shutil", "socket", "ctypes", "multiprocessing", "threading", "pickle"]
|
||||
# Add file writing modules to the blacklist
|
||||
dangerous_modules = [
|
||||
"os", "sys", "subprocess", "shutil", "socket", "ctypes",
|
||||
"multiprocessing", "threading", "pickle",
|
||||
# Additional modules that can write files
|
||||
"tempfile", "pathlib", "io", "fileinput"
|
||||
]
|
||||
tree = ast.parse(code)
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.Import):
|
||||
@@ -108,35 +158,104 @@ def detect_dangerous_imports(code):
|
||||
return node.module
|
||||
return None
|
||||
|
||||
def detect_file_write_operations(code):
|
||||
"""Detect potential file writing operations in code"""
|
||||
dangerous_patterns = [
|
||||
'open(', 'file(', 'write(', 'writelines(',
|
||||
'with open', 'f.write', '.write(',
|
||||
'create', 'mkdir', 'makedirs'
|
||||
]
|
||||
|
||||
for pattern in dangerous_patterns:
|
||||
if pattern in code:
|
||||
return f"File write operation detected: {pattern}"
|
||||
return None
|
||||
|
||||
def run_pythonCode(data:dict):
|
||||
if not data or "code" not in data or "variables" not in data:
|
||||
return {"error": "Invalid request format"}
|
||||
code = data["code"]
|
||||
if not data or "code" not in data:
|
||||
return {"error": "Invalid request format: missing code"}
|
||||
|
||||
code = data.get("code")
|
||||
if not code or not code.strip():
|
||||
return {"error": "Code cannot be empty"}
|
||||
|
||||
code = remove_print_statements(code)
|
||||
dangerous_import = detect_dangerous_imports(code)
|
||||
if dangerous_import:
|
||||
return {"error": f"Importing {dangerous_import} is not allowed."}
|
||||
variables = data["variables"]
|
||||
|
||||
# Check for file write operations
|
||||
write_operation = detect_file_write_operations(code)
|
||||
if write_operation:
|
||||
return {"error": f"File write operations are not allowed: {write_operation}"}
|
||||
|
||||
# Handle variables - default to empty dict if not provided or None
|
||||
variables = data.get("variables", {})
|
||||
if variables is None:
|
||||
variables = {}
|
||||
|
||||
imports = "\\n".join(extract_imports(code))
|
||||
var_def = ""
|
||||
output_code = "if __name__ == '__main__':\\n res = main("
|
||||
|
||||
# Process variables with proper validation
|
||||
for k, v in variables.items():
|
||||
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)"
|
||||
if not isinstance(k, str) or not k.strip():
|
||||
return {"error": f"Invalid variable name: {repr(k)}"}
|
||||
|
||||
# Use repr() to properly handle Python True/False/None values
|
||||
try:
|
||||
one_var = f"{k} = {repr(v)}\\n"
|
||||
var_def = var_def + one_var
|
||||
except Exception as e:
|
||||
return {"error": f"Error processing variable {k}: {str(e)}"}
|
||||
|
||||
# Create a safe main function call with error handling
|
||||
output_code = '''if __name__ == '__main__':
|
||||
import inspect
|
||||
try:
|
||||
# Get main function signature
|
||||
sig = inspect.signature(main)
|
||||
params = list(sig.parameters.keys())
|
||||
|
||||
# Create arguments dict from available variables
|
||||
available_vars = {''' + ', '.join([f'"{k}": {k}' for k in variables.keys()]) + '''}
|
||||
|
||||
# Match parameters with available variables
|
||||
args = []
|
||||
kwargs = {}
|
||||
|
||||
for param_name in params:
|
||||
if param_name in available_vars:
|
||||
args.append(available_vars[param_name])
|
||||
else:
|
||||
# Check if parameter has default value
|
||||
param = sig.parameters[param_name]
|
||||
if param.default is not inspect.Parameter.empty:
|
||||
break # Stop adding positional args, rest will use defaults
|
||||
else:
|
||||
raise TypeError(f"main() missing required argument: '{param_name}'. Available variables: {list(available_vars.keys())}")
|
||||
|
||||
# Call main function
|
||||
if args:
|
||||
res = main(*args)
|
||||
else:
|
||||
res = main()
|
||||
|
||||
print(res)
|
||||
except Exception as e:
|
||||
print({"error": f"Error calling main function: {str(e)}"})
|
||||
'''
|
||||
code = imports + "\\n" + seccomp_prefix + "\\n" + var_def + "\\n" + code + "\\n" + output_code
|
||||
|
||||
# Note: We still need to create the subprocess file for execution,
|
||||
# but user code cannot write additional files
|
||||
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", tmp_file], capture_output=True, text=True, timeout=10)
|
||||
if result.returncode == -31:
|
||||
return {"error": "Dangerous behavior detected."}
|
||||
return {"error": "Dangerous behavior detected (likely file write attempt)."}
|
||||
if result.stderr != "":
|
||||
return {"error": result.stderr}
|
||||
|
||||
|
@@ -12,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 = {}
|
||||
@@ -111,13 +111,31 @@ export const runJsSandbox = async ({
|
||||
}
|
||||
};
|
||||
|
||||
const PythonScriptFileName = 'main.py';
|
||||
export const runPythonSandbox = async ({
|
||||
code,
|
||||
variables = {}
|
||||
}: RunCodeDto): Promise<RunCodeResponse> => {
|
||||
// Validate input parameters
|
||||
if (!code || typeof code !== 'string' || !code.trim()) {
|
||||
return Promise.reject('Code cannot be empty');
|
||||
}
|
||||
|
||||
// Ensure variables is an object
|
||||
if (variables === null || variables === undefined) {
|
||||
variables = {};
|
||||
}
|
||||
if (typeof variables !== 'object' || Array.isArray(variables)) {
|
||||
return Promise.reject('Variables must be an object');
|
||||
}
|
||||
|
||||
const tempDir = await mkdtemp(join(tmpdir(), 'python_script_tmp_'));
|
||||
const dataJson = JSON.stringify({ code, variables, tempDir });
|
||||
const dataBase64 = Buffer.from(dataJson).toString('base64');
|
||||
const mainCallCode = `
|
||||
data = ${JSON.stringify({ code, variables, tempDir })}
|
||||
import json
|
||||
import base64
|
||||
data = json.loads(base64.b64decode('${dataBase64}').decode('utf-8'))
|
||||
res = run_pythonCode(data)
|
||||
print(json.dumps(res))
|
||||
`;
|
||||
@@ -173,7 +191,6 @@ async function createTempFile(tempFileDirPath: string, context: string) {
|
||||
return {
|
||||
path: tempFilePath,
|
||||
cleanup: () => {
|
||||
rmSync(tempFilePath);
|
||||
rmSync(tempFileDirPath, {
|
||||
recursive: true,
|
||||
force: true
|
||||
|
Reference in New Issue
Block a user