import os import shlex import shutil file_id = (params_kw or {}).get('id', '').strip() uid = await get_user() if not uid: # 安全:未登录直接拒绝。此前回退成 'user-01'(admin) —— 结合 RBAC 通配授权, # 未登录者可拿到 admin 工作空间的编辑器进程(bwrap 沙箱内仍可读写工作空间 # 全部源码与 env/*.json,后者含 DB 密码)。禁止匿名回退。 r = DictObject() r.host = 'localhost' r.username = 'pipeline' r.cmdargs = ['echo 未登录,拒绝访问'] r.noinput = True return r session_id = (params_kw or {}).get('session_id', '') or '' pipeline_id = (params_kw or {}).get('pipeline_id', '') or '' dbname = get_module_dbname('pipeline-sdlc') async with DBPools().sqlorContext(dbname) as sor: # 产线隔离 + pipeline_id 透传(2026-09-17 修复:此前不读 pipeline_id, # 通用助手 _generic 入口下解析到全局指针的错误项目空间 → 「未指定文件」/编辑错目录) project_dir, _ = await get_project_dir_pl(sor, uid, session_id, pipeline_id) space_dir, _ = await get_space_dir(sor, uid, session_id) # 通用会话(pipeline_id=_generic):锁用户专属目录 _general/{uid} if pipeline_id == '_generic': _gd = generic_workspace_dir(uid) os.makedirs(_gd, exist_ok=True) project_dir = _gd space_dir = _gd if not file_id or not space_dir: r = DictObject() r.host = 'localhost' r.username = 'pipeline' r.cmdargs = ['echo 未指定文件'] return r full_path = resolve_workspace_path(project_dir, space_dir, file_id) # 路径穿越校验 real_ws = os.path.realpath(space_dir) real_full = os.path.realpath(full_path) if not real_full.startswith(real_ws + os.sep): r = DictObject() r.host = 'localhost' r.username = 'pipeline' r.cmdargs = ['echo 非法路径'] return r # 相对 space_dir 的路径(bwrap 挂载 space_dir 到 /home,vi 用相对路径) rel_path = os.path.relpath(full_path, space_dir) # bwrap 沙箱路径(不存在则拒绝终端,安全优先,不给完整 shell) bwrap = shutil.which('bwrap') if not bwrap and os.path.exists('/d/pipeline/pipeline-app/bin/bwrap'): bwrap = '/d/pipeline/pipeline-app/bin/bwrap' r = DictObject() r.host = 'localhost' r.username = 'pipeline' if not bwrap: r.cmdargs = ['echo 沙箱不可用,已拒绝终端访问'] return r # bwrap 沙箱:只暴露 workspace(挂载到 /home 可写)+ 只读系统目录, # 隔离 user/pid/ipc/uts/net,防 rm -rf /、sudo 提权、越界访问、网络攻击 cmd = ( bwrap + ' --unshare-user --unshare-pid --unshare-ipc --unshare-uts --unshare-net' ' --die-with-parent' ' --ro-bind /usr /usr' ' --ro-bind /bin /bin' ' --ro-bind /sbin /sbin' ' --ro-bind /lib /lib' ' --ro-bind /lib64 /lib64' ' --ro-bind /etc /etc' ' --proc /proc' ' --dev /dev' ' --tmpfs /tmp' ' --bind ' + shlex.quote(real_ws) + ' /home' ' --chdir /home' ' --setenv HOME /home' ' -- vi ' + shlex.quote(rel_path) ) r.cmdargs = [cmd] return r