fix(isolation): 通用会话剔除shell类工具+上传落专属目录

- load_agent_config generic 时剔除 category=shell(run_command 无法圈禁在工作目录内,
  拿到即可遍历整个工作空间枚举各产线项目——第6层泄漏的 schema 层防线)
- upload_tools.resolve_upload_dir 加 generic 参数:通用会话上传落 _general/{uid}
  专属目录,与 AgentExecutor 文件根一致
- agent_chat_generic.dspy: 上传解析传 generic=True
This commit is contained in:
yumoqing 2026-09-05 15:21:51 +08:00
parent e9b1ba37ca
commit 523ddf451d
3 changed files with 12 additions and 3 deletions

View File

@ -389,8 +389,10 @@ async def load_agent_config(pipeline_id: str = None, project_id: str = None, gen
# 的 schema 里会出现 switch_project/project_info/list_my_projects 等,
# LLM 看到就能查看/切换/操作任何产线的项目(含他人产线的)。
# 执行层在 agent_loop_v2._dispatch_sdlc_tool 还有一道拦截兜底。
# 同时剔除 shell 类工具run_commandshell 无法圈禁在工作目录内,
# 通用会话拿到就能遍历整个工作空间枚举各产线项目(第 6 层泄漏)。
if generic:
tools = [t for t in tools if t.category != "project"]
tools = [t for t in tools if t.category not in ("project", "shell")]
prompt = base.system_prompt
if ability:
tools = _merge_tools(ability.tools, tools)

View File

@ -34,12 +34,16 @@ def extract_text(path, name):
return '', 0, False
async def resolve_upload_dir(sor, uid, session_id=''):
async def resolve_upload_dir(sor, uid, session_id='', generic: bool = False):
"""上传文件落盘目录:有当前项目 → 项目根目录;无项目 → workspace 会话目录。
两个位置都在会话 agent read_file 根内项目根 / WORKSPACE_BASE
保证上传放的位置 = agent 读的位置
返回 (dir, rel_prefix)rel_prefix 是相对 read_file 根的路径前缀项目根为 ''
generic=True纯通用会话2026-09-05 产线隔离无项目时落用户专属
通用工作目录 _general/{uid} AgentExecutor 的文件根一致
_resolve_ws_path 越界保护只认该目录rel_prefix ''
"""
try:
from pipeline_service.workspace import (
@ -54,6 +58,9 @@ async def resolve_upload_dir(sor, uid, session_id=''):
return pdir, ''
except Exception:
pass
if generic:
from pipeline_service.workspace import generic_workspace_dir
return generic_workspace_dir(uid), ''
sess = (session_id or uid or 'default').replace('/', '_').replace('..', '_')
return os.path.join(WORKSPACE_BASE, '_uploads', sess), '_uploads/' + sess + '/'

View File

@ -50,7 +50,7 @@ if action == 'send_message':
try:
_dbname = get_module_dbname('pipeline_core')
async with DBPools().sqlorContext(_dbname) as sor:
_udir, _rel = await resolve_upload_dir(sor, uid, '')
_udir, _rel = await resolve_upload_dir(sor, uid, '', generic=True)
_saved = save_uploads(_udir, _uploads)
_byname = {os.path.basename(src): n for (src, _n), (n, _p) in zip(_uploads, _saved)}
_items = []