fix(isolation): 第6层泄漏——通用会话工作空间总根可枚举所有产线项目

实测发现:工具层隔离生效后,LLM 改用 list_files/run_command 浏览
WORKSPACE_BASE 总根({base}/{org}/{产线}/…全量可见),从目录名
枚举出所有产线的全部项目。

修复:
- workspace.py 新增 generic_workspace_dir():通用会话专属目录 _general/{uid}
- agent_loop_v2: generic 会话文件根改专属目录(_resolve_ws_path 越界保护圈死)
- agent_loop_v2: run_command 对通用会话执行层拒绝(shell 无法圈禁在目录内)
- schema 层剔除 shell 类工具(配合 pipeline-core)
This commit is contained in:
ymq 2026-09-05 15:21:35 +08:00
parent b99fb4cc13
commit 5c28fed040
2 changed files with 35 additions and 0 deletions

View File

@ -55,6 +55,12 @@ _PROJECT_TOOL_NAMES = {
"pause_project", "resume_project", "delete_project", "pause_project", "resume_project", "delete_project",
} }
# 产线耦合工具(2026-09-05 第 6 层泄漏修复):run_command 的 shell 无法圈禁
# 在目录内(cwd 只是起点,命令可 cd/绝对路径访问全工作空间),纯通用会话
# 一律剔除。read_file/list_files/search_files/write_file 受 _resolve_ws_path
# 越界保护,配合通用会话专属工作目录(_general/{user})天然隔离,保留。
_GENERIC_DENIED_TOOLS = {"run_command"}
# ── 默认工具定义(在 pipeline-core 未加载时使用)── # ── 默认工具定义(在 pipeline-core 未加载时使用)──
@ -457,6 +463,18 @@ class AgentExecutor:
# 6. 项目空间键:generic→'general'(通用助手),否则用真实 pipeline_id(产线之间隔离) # 6. 项目空间键:generic→'general'(通用助手),否则用真实 pipeline_id(产线之间隔离)
self.space = GENERAL_SPACE if self.generic else (self.pipeline_id or GENERAL_SPACE) self.space = GENERAL_SPACE if self.generic else (self.pipeline_id or GENERAL_SPACE)
# 7. 通用会话专属工作目录(2026-09-05 第 6 层泄漏修复):默认文件根是
# WORKSPACE_BASE 总根({base}/{org}/{产线}/… 全量可见),LLM 用
# list_files/read_file 就能从目录名枚举所有产线的项目。通用会话改用
# 用户专属目录 _general/{user_id},_resolve_ws_path 越界保护把它圈死。
if self.generic:
from .workspace import generic_workspace_dir
self.workspace_dir = generic_workspace_dir(self.user_id)
try:
os.makedirs(self.workspace_dir, exist_ok=True)
except Exception:
pass
async def _resolve_skills_base_dir(self): async def _resolve_skills_base_dir(self):
"""技能根目录 = 全局 skills/(单一技能树,所有机构共享读,2026-08-21 重构)。""" """技能根目录 = 全局 skills/(单一技能树,所有机构共享读,2026-08-21 重构)。"""
try: try:
@ -809,6 +827,11 @@ class AgentExecutor:
if self.generic and tool_name in _PROJECT_TOOL_NAMES: if self.generic and tool_name in _PROJECT_TOOL_NAMES:
return ("FAIL: 当前是通用助手会话(未挂产线插件),不提供项目管理能力," return ("FAIL: 当前是通用助手会话(未挂产线插件),不提供项目管理能力,"
"无法查看或操作任何产线的项目。") "无法查看或操作任何产线的项目。")
# 产线耦合工具拦截(2026-09-05 第 6 层):run_command 的 shell 无法圈禁
# 在工作目录内,通用会话一律拒绝。
if self.generic and tool_name in _GENERIC_DENIED_TOOLS:
return ("FAIL: 当前是通用助手会话,不提供命令执行能力。"
"如需处理文件,可直接上传文件,我会读取上传内容作答。")
# 通用内建工具映射(core 内核,产线无关)。 # 通用内建工具映射(core 内核,产线无关)。
# 产线专属工具(create_task/diagnose_project 等)已迁移到 sdlc_ability 能力包, # 产线专属工具(create_task/diagnose_project 等)已迁移到 sdlc_ability 能力包,

View File

@ -15,6 +15,18 @@ WORKSPACE_BASE = '/d/pipeline/workspaces'
GENERAL_SPACE = 'general' GENERAL_SPACE = 'general'
def generic_workspace_dir(user_id: str) -> str:
"""纯通用会话的专属工作目录(2026-09-05 产线隔离第 6 层修复)。
通用会话没有项目,文件工具的文件根若用 WORKSPACE_BASE 总根,
LLM 可浏览 {base}/{org}/{产线}/… 从目录名枚举所有产线的项目。
改为每用户专属目录 _general/{user_id},配合 _resolve_ws_path 的
越界保护,把通用会话的文件视野圈死在自己的目录内。
"""
uid = (user_id or 'anonymous').replace('/', '_').replace('..', '_') or 'anonymous'
return os.path.join(WORKSPACE_BASE, '_general', uid)
def build_workspace_path(workspace_base, org_id, space, name, project_id=''): def build_workspace_path(workspace_base, org_id, space, name, project_id=''):
"""构建项目 workspace 路径:{base}/{org_id}/{space}/{name}。 """构建项目 workspace 路径:{base}/{org_id}/{space}/{name}。