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:
Archer
2025-09-15 20:02:54 +08:00
committed by GitHub
parent c8934e3d22
commit 2ed1545eb5
187 changed files with 3701 additions and 2221 deletions

View File

@@ -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}

View File

@@ -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