与 f0c4c10(upload_url) 同族缺陷全量排查修复,共5处:
1. workspace_view.dspy: md_url(file_url)只拼session_id漏pipeline_id→
通用助手(_generic无session_id)下MdWidget二次请求workspace_file.dspy
回退全局项目指针→在别产线空间找文件→弹'文件不存在: projects/调研1/docs/xxx.md'
(2026-09-17实测复现+修复验证)
2. workspace_open.dspy: file_url/ws_url(xterm)/univer_url三处同病灶,统一_fq_s双透传
3. workspace_edit.xterm: 根本不读pipeline_id且用无隔离get_project_dir→
改get_project_dir_pl+_generic锁_general/{uid}(与9个子端点对齐)
4. office_read.dspy/office_save.dspy: 读了pipeline_id但缺_generic分支→
通用助手在线编辑docx报'未指定文件或无工作空间',补齐分支
univer-office前端main.ts配套透传pipeline_id(独立仓库另行部署)
93 lines
3.1 KiB
Plaintext
93 lines
3.1 KiB
Plaintext
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
|