pipeline-sdlc/wwwroot/workspace_edit.xterm
ymq b02ee5f345 fix(workspace): 二级URL与office端点透传pipeline_id+_generic分支——通用助手查看/编辑文件报'文件不存在'根因
与 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(独立仓库另行部署)
2026-09-17 23:27:19 +08:00

93 lines
3.1 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 到 /homevi 用相对路径)
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