漏洞

mcp-atlassian 附件上传可读取工作区外文件

GHSA-g5r6-gv6m-f5jv 影响 mcp-atlassian 0.22.0 之前版本:Confluence 附件工具会把调用者提供的 file_path 一路交给本地 open() ,没有先证明文件仍位于工作区;一段不可信内容因此可能借模型之手,把服务进程可读的主机文件变成远端附件。

手绘调查场景中,一张附件请求卡片沿红线穿过工作区围栏,连接到主机文件抽屉和远端协作页面,表现本地读取与远端写入被同一次工具调用串联。
文章导航

研究依据公告 GHSA-g5r6-gv6m-f5jv · 受影响版本 < 0.22.0 · 修复提交 b041733473f95119dd539542a43c280737a8e460 · 首个修复版本 0.22.0

来源mcp-atlassian 安全公告 / 上游源码与测试 / MCP、Python 与 Atlassian 官方文档 / SOSEC 源码复核

1 附件已经上传成功,问题却发生在它被命名之前

复盘这类事件时,最容易被忽略的往往不是一条失败日志,而是一条完全成功的业务记录:某个 Confluence 页面多出了一份附件,创建者是团队熟悉的集成账号,HTTP 返回正常,文件名也像普通构建产物。页面管理员看到的是一次合规上传,主机管理员看到的却可能是同一时刻对工作区之外文件的读取。两边的系统都完成了各自被交付的任务,危险恰恰藏在这份“没有报错”的一致性里。

mcp-atlassian 本来要解决一个很朴素的问题:让支持 Model Context Protocol 的客户端把 Jira 与 Confluence 纳入工具工作流。用户可以检索议题、更新页面,也可以把本地产物作为附件发送到协作空间。附件能力天然需要两种权限——在主机上读出字节,在远端用已经配置的身份写入内容。0.22.0 之前,这两种权限在一个 file_path 参数上直接相遇,却没有第三个判断来回答:这些字节是否属于这次工具调用获准处理的工作区。

这不是“Confluence 接受了危险文件类型”的故事。远端服务收到的是普通分段表单上传,认证、页面权限、附件版本与审计都照常工作。真正失去约束的是文件选择:只要 mcp-atlassian 进程账号能够读取,绝对路径、父目录跳转或指向外部的符号链接就可能让工具碰到原本不属于项目产物的对象。远端系统随后把这些字节保存下来,使一次瞬时本地读取变成可搜索、可下载、可进入备份周期的持久副本。

公告给出的端到端场景进一步揭示了为什么这条路径值得单独研究。不可信 Jira 内容影响模型规划,模型调用 Confluence 附件工具,并把 Linux 的 /proc/self/environ 当作上传对象。文件本身没有“越权”标记,工具调用也使用了有效的集成凭据;只有把内容来源、工具参数、本地文件句柄和远端附件四段记录放在同一条时间线上,才能看出一项协作自动化已经完成了主机到云端的数据披露。

处置所需的代码与版本判断可以在这里一次说清:受影响范围是 mcp-atlassian < 0.22.0;Confluence 的关键路径从 src/mcp_atlassian/confluence/attachments.py 中的 AttachmentsMixin.upload_attachment() 进入 _upload_attachment_direct(),后者以 open(file_path, "rb") 创建本地句柄。提交 b041733473f95119dd539542a43c280737a8e460 在 0.22.0 中把 validate_safe_path() 接入 Confluence 与 Jira 上传入口。永久修复需要升级并重启所有旧进程,再把 Path.cwd() 对应的工作根收窄到专用产物目录;升级前可临时移除本地附件工具、撤销远端写入,或让进程只看到空的专用暂存区。提示词、模型更换和确认窗口都不能代替服务端路径约束;若已出现可疑附件,还要保全元数据与调用记录、限制远端副本,并按内容轮换凭据。

调查因此从附件落地后的那一刻倒着走:先找到远端附件 ID,再回到发起请求的 MCP 会话,继续沿参数进入 FastMCP 工具、获取组件、附件混入类和最终 open()。这条逆向路线不会把“模型受骗”当作解释终点。模型决定为什么会发起调用,服务端代码决定这次调用究竟能读取什么;只有后一个约束被写成可验证的文件系统策略,任意客户端和自动化入口才会得到同样的保护。

1.1 一次正常的远端写入,借用了过大的本地读取能力

附件工具对操作员呈现的对象很简单:目标页面、文件路径、可选评论,以及是否把更新标记为 minor_edit。目标页面决定写到哪里,file_path 决定从哪里拿字节。问题在于两者的授权语义并不对称。Confluence 可以判断集成身份是否有权修改页面,却无法知道本地路径是否属于某个项目;操作系统可以判断进程账号能否读取文件,却不知道本次远端发布是否得到数据所有者同意。

当这两个局部判断直接串联时,“进程可读”就被误当成了“工具可发布”。在桌面客户端中,进程可能继承用户主目录与源码仓库的读取权;在容器中,它可能同时看到工作目录、环境注入、服务账号挂载和调试卷;在共享 MCP 服务中,它还可能替多个用户复用同一套 Atlassian 凭据。任何一种部署都可能让工具的真实可见范围明显大于界面里那一个看似窄小的附件按钮。

风险不是由文件名决定。一个名为 environconfigcredentials 的附件不会自动触发远端拒绝,basename 甚至会抹掉原路径的大部分上下文。若上传的是 /home/service/.config/example/token.json,页面上可能只留下 token.json;若同名附件已经存在,远端还可能把它保存成新版本。调查者若只按页面标题或扩展名搜索,很容易错过真正需要复核的对象。

这条能力组合也解释了 CVSS 7.7 的重点。漏洞不替攻击者绕过操作系统 ACL,也不会凭空获得 Atlassian 管理员权限;它利用的是集成已经拥有的两端能力,把本来互不等价的授权连接起来。影响上限由三个集合的交集决定:进程账号可读的本地对象、调用者能够选择的工具参数,以及集成身份可写且他人可见的远端空间。任何盘点若缺少其中一项,都会高估或低估真实暴露。

修复后的目标也由此变得清楚:不是禁止附件上传,而是在本地读取发生前引入一个稳定、可测试的根目录。合法产物仍能沿原路径发送,超出根目录的候选路径则在文件句柄创建之前停止。这个变化把“我们相信模型会挑对文件”改成“无论谁挑文件,服务端只能打开这棵目录树中的对象”,让产品功能与数据范围第一次拥有同一份契约。

手绘桌面把调用者、附件参数卡、工作区文件、集成凭据与远端页面连成一条路径,突出一次工具调用同时使用本地读取和远端写入两种能力。
图 1|远端写入是否获准,与本地文件是否属于工作区,是两个不同问题;旧实现只得到了两端账号权限的“是”,没有询问数据范围。

1.2 公告固定了版本、提交与影响,剩下的必须回到源码证明

GitHub 安全公告 GHSA-g5r6-gv6m-f5jv 于 2026 年 7 月 10 日公开,标记为高危,影响所有低于 0.22.0 的 mcp-atlassian 版本,首个修复版本是 0.22.0。公告没有分配 CVE,因此资产库、告警和工单都应保留 GHSA 标识,不能等待一个尚不存在的 CVE 字段再开展处置。使用镜像或下游打包时,还要核对实际安装代码;产品展示的版本字符串不足以单独完成判断。

上游修复提交是 b041733473f95119dd539542a43c280737a8e460,父提交为 4067d1db4097755cc6add87f95fe3f0746c98c0b。该提交进入 v0.22.0,并继续存在于后续标签。它是一组安全加固中的一部分,包含不止一项公开变化;本文只追踪附件路径的根目录限制,不把同一提交中的其他修复混成一条攻击链。这样做既避免扩大公告范围,也便于下游回移时精确核对相关文件。

公开结论给出了起点,源码负责填满中间的因果。我们在受影响标签核对 FastMCP 工具定义、Confluence 获取组件、AttachmentsMixin 与直接上传辅助函数,再在修复标签核对 validate_safe_path()、Confluence 与 Jira 的调用点以及对应测试。每一层只回答一个问题:参数怎样进入、在哪一层改变形态、何处创建本地句柄、修复在哪个共享入口生效。

这种提交级复核还防止一个常见误读:把“增加了 Base64 内容上传模式”当作漏洞修复本身。客户端直接提交已持有的字节,确实不再要求服务端自行选择本地路径;但为了兼容已有工作流,file_path 模式仍然存在。真正改变安全属性的是路径在进入 open() 前完成规范化解析与根目录归属检查;新增参数形式本身没有提供这项约束。

对于公开材料没有覆盖的部署差异,结论保持条件式。例如 Windows 服务的当前工作目录、容器挂载内容和封装程序的依赖解析必须由组织自行测得;上游事实只能说明默认代码怎样工作。把“上游已确认”与“本地已验证”分成两列,可以让处置负责人看到哪些事项已经固定,哪些仍需要现场输入。

1.3 从页面附件倒推主机读取,案件才真正开始

远端附件是最直观的结果,却不是完整证据。它能提供附件 ID、内容 ID、创建者、文件名、大小、时间、版本与页面权限;有些环境还能得到下载或审计记录。这些字段证明某些字节在某个时刻进入了协作空间,但 basename 无法回答原始绝对路径,文件内容也不应在未授权的情况下被随意再次打开。调查需要用时间、请求 ID 和集成身份继续向主机侧关联。

MCP 客户端或服务端日志应当提供工具名、会话、来源内容、参数、目标页面与响应。历史版本可能把原始 file_path 直接写入日志,这对定位很有价值,同时也会使日志本身成为敏感材料。较新的遥测可以记录路径的策略结果和经过脱敏的根目录关系,不记录密钥内容。若没有结构化工具日志,客户端对话记录、进程标准输出、反向代理和 Atlassian 请求时间仍可共同缩小窗口。

主机一侧需要寻找的是文件打开而非文件修改。Linux audit、eBPF、容器运行时或 EDR 文件事件能够显示 mcp-atlassian 进程在相邻时间读取了哪些对象;网络侧则把同一进程或容器的出站请求连接到 Atlassian 接口。三类证据不一定共享同一个 ID,因此校正 UTC、记录时钟偏差、保留进程生命周期和容器重启时点,比简单按“同一分钟”匹配更可靠。

一旦文件可能包含凭证,案件会再延伸一层。远端附件的存在只是披露事件,真正的后续影响取决于谁看过、是否下载、是否进入通知、导出、搜索索引或备份,以及其中的令牌是否随后被使用。处置不能等到所有查看者都确定后才轮换高价值密钥;可以先从可信环境撤销并替换,再继续保存访问证据,避免调查过程延长可利用窗口。

开场那份看似普通的附件由此变成一条可复算的状态转换:调用前,字节只存在于主机可读对象;调用中,路径被当成工具参数并形成文件句柄;调用后,同一内容获得远端附件 ID、权限和保留周期。后文要解释的每一层代码、每一种路径输入和每一项响应动作,都围绕这三个状态之间究竟缺少了哪一次授权判断展开。

这一做法也约束叙事。我们可以确认受影响代码允许读取工作区之外的文件并将字节提交到远端,也可以确认公告演示了提示注入路径;某个具体组织是否发生泄露、泄露了哪些文件、谁曾查看,仍需它自己的日志回答。把产品能力与现场事件分开书写,使技术结论足够明确,同时不给缺失证据填上想象中的细节。

2 顺着 file_path 走五层,直到它变成一个可发送的文件句柄

源码中的危险并不藏在一段晦涩算法里,而是分散在几层都很合理的转交之中。工具层负责把参数变成业务调用,获取组件选择具体产品实现,混入类处理文件名与批量复用,直接上传辅助函数负责分段表单,HTTP 客户端使用已经配置的会话发送。每一层都保留上一层赋予 file_path 的含义,结果就是最初那段不可信字符串在抵达本地 I/O 前从未被重新解释为一项需要授权的资源选择。

2.1 FastMCP 暴露的是一个业务动作,不是裸文件系统接口

src/mcp_atlassian/servers/confluence.py 注册异步 upload_attachment()。受影响版本的核心输入包括 Confluence 内容 ID、file_path、可选 comment 与 minor_edit。从参数结构看,它像任何一个附件表单:调用者选择目标与本地文件,服务端代为完成上传。正因为接口使用的是业务语言,用户很难直觉感知它背后继承了整个进程账号的文件读取范围。

工具函数先取得已经配置的 Confluence 获取组件。这个对象持有基础 URL、认证会话、SSL 与代理等远端连接状态,因此后续代码无需调用者再次提供凭据。接着,工具把内容 ID、路径和附加字段原样交给 confluence_fetcher.upload_attachment()。这里已经出现了第一次身份分离:谁选择文件来自 MCP 会话,谁向远端写入则是服务端预先配置的集成身份。

FastMCP 的工具描述曾允许绝对路径,也说明相对路径按照当前工作目录解释。这种描述有利于自动化,因为模型可以引用明确文件;它也等于公开承诺服务端会替调用者解析主机路径。若实现没有同时给出允许根目录,绝对路径便不是“更精确的工作区地址”,而是直接进入进程可读命名空间。参数类型仍是字符串,权限含义却已从业务对象悄悄扩张为主机对象。

工具入口本身不是唯一可能调用上传逻辑的位置。库用户可以绕过 MCP 封装程序直接使用获取组件,批量工具也会把多条路径拆开后复用同一附件方法。因此,把检查只放在 FastMCP 函数中,会让界面入口安全、底层复用仍旧危险。修复把校验接到各产品的附件入口,所有本地路径都要先经过这里,之后才会进入直接上传函数。

0.22.0 还增加了 content_base64 分支,让客户端直接提交自己已经持有的内容。这个模式改变了数据所有者:服务端不再根据字符串挑选主机文件,而是接收请求体中的字节。不过,两种输入必须互斥,大小与日志也需要独立治理。它降低了本地路径选择的必要性,却没有替代对保留 file_path 分支的目录约束。

五个手绘工作站从工具参数、fetcher、附件 mixin、本地文件句柄延伸到远端附件,调用者身份逐渐淡出,而集成凭据与文件字节逐渐变得具体。
图 2|字段一路被转交:调用者选择路径,服务端选择本地对象,预配置身份完成远端写入;旧链路中没有任何一站同时核对这三件事。

2.2 AttachmentsMixin 改了路径形态,却没有改变它的权限

获取组件最终进入 src/mcp_atlassian/confluence/attachments.pyAttachmentsMixin.upload_attachment()。受影响代码会把相对输入转成绝对地址,检查对象存在,再用 os.path.basename() 取得远端附件名。对稳定上传来说,这些都是合理步骤:调用方不必关心进程从哪里启动,远端也不会收到一串本地目录作为显示名称。

然而,abspath 回答的是“这段字符串在当前目录下指向哪里”,不是“它是否仍在允许目录里”。父目录成分会被折叠,绝对输入保持绝对,结果只会更确定,不会更受限。存在性检查同样只证明对象能被找到;操作系统 ACL 也只证明服务账号可以读取。三个判断加在一起,依然没有表达项目、租户或工具会话的授权范围。

混入类随后调用 _upload_attachment_direct()。直接上传辅助函数组装 /rest/api/content/{content_id}/child/attachment,设置 Atlassian 上传要求的请求字段,并用 open(file_path, "rb") 创建文件对象。分段表单的 file 字段从这个句柄读取字节,评论与 minor_edit 标记作为其他字段发送。此刻,候选字符串终于造成真实的本地访问。

网络请求成功后,远端返回附件 ID、标题、大小和链接等信息;同名对象可能形成新版本。辅助函数的 finally 分支会关闭文件句柄,因而不会表现为持续锁定或明显资源泄漏。对于传统稳定性监控,这是一段干净的成功路径:打开、发送、关闭都完成。安全异常只存在于“打开的究竟是哪一个文件”这项业务之外的事实。

批量上传并没有创造不同根因。逗号分隔的输入被拆成多条候选路径,每一条最终进入相同混入类;如果其中一项指向工作区外,泄露只是从一次变成多次。把修复放在混入类后,单文件、批量和直接库调用都能复用相同规则,拒绝也发生在直接上传辅助函数之前,保证失败请求不会留下远端半成品。

上传数据在直接上传辅助函数中还会经过文件名、MIME 推断、HTTP 重试和远端版本语义。它们可以改变附件如何显示与请求是否重复,却不会收窄来源。即使扩展名白名单阻止某些类型,敏感文本仍能使用常见后缀或无后缀名称;即使远端执行恶意内容扫描,扫描目标也是文件内容风险,并非本地数据所有权。

2.3 调用者、进程与远端身份在同一参数上错位

从身份账本看,至少有三位主体。第一位是触发工具的用户、模型会话或自动化;第二位是运行 mcp-atlassian 的操作系统账号;第三位是向 Atlassian 认证的 OAuth 或 API-令牌身份。第一位能够影响路径,第二位决定可读文件,第三位决定附件去向与可见范围。旧实现没有要求三者的授权交集被显式表示,只要每一位分别能完成自己的局部动作,整条链就会成功。

共享服务会把这种错位放大。多个调用者可能通过不同客户端使用同一个服务端,而服务端为效率复用同一进程与远端会话。某位用户对项目 A 的操作意图,可能借到包含项目 B、运维配置或 service 密钥的主机视野,再写入集成账号有权修改的另一个空间。即便产品没有多租户承诺,这种身份复用也要求部署方把工作目录与挂载按最小用途切分。

桌面形态同样不能被视为低风险。子进程常继承启动器的当前目录、用户环境和主目录权限;用户可能把 MCP 服务端从仓库根、用户主目录甚至系统服务目录启动。界面里只显示一个附件工具,实际根目录却由启动方式静默决定。修复把默认基准目录绑定到 Path.cwd() 后,当前目录从实现细节变成安全配置,必须进入安装文档和启动验收。

远端权限也改变了披露半径。附件落在受限个人页面、内部团队空间或允许外部访客的知识库,后果不同;页面继承、自动通知、索引、导出和备份又会继续复制内容。调查不能只写“上传至 Confluence”,而要记录空间、页面、可查看群组、历史版本、下载者和保留策略。只有把目标身份的权限图展开,才能判断哪些秘密需要立刻轮换。

权限变化也要进入账本。集成令牌可能在事件窗口内被扩权或轮换,页面可能移动到不同空间,服务账号的组成员与挂载也会随部署改变。调查若只采集当前状态,会把历史可见性误投射到过去。配置审计、基础设施版本和页面权限历史能够为每个时间段重建较接近真实的交集。

对于多团队共享服务,建议给每套远端身份配置独立进程与独立暂存根目录。这样,本地可读集合和远端可写集合在部署时就已配成一对;一个实例即使被错误调用,也不会借到其他团队的目录或空间。资源成本会增加,换来的却是更小的调查半径、清晰的日志归属和可单独吊销的凭据。

3 路径变得规范,并不意味着它仍然属于工作区

评审文件访问范围时,一句“路径已经规范化”常让讨论过早结束。这个说法把两种不同性质混在一起:规范化消除字符串表达的歧义,根目录归属检查证明解析后的对象仍在允许范围内。前者让程序更确定地找到文件,后者才限制程序能够找到哪些文件。旧实现具备前者而缺少后者,所以它理解了地址,却没有判断这次调用是否有权使用该地址。

一套完整的路径矩阵至少要覆盖绝对路径、父目录跳转、名称前缀相似的兄弟目录、符号链接、不同盘符或共享路径,以及启动目录变化。单独拿任何一种输入当作漏洞全貌都会遗漏其他等价表达。修复先得到规范化路径,再按目录组件检查归属,把多种语法收敛到同一个问题:最终目标是否仍在解析后的受控根目录内。

3.1 abspath 解决定位,is_relative_to 才回答归属

假设进程从 /srv/mcp/workspace 启动,输入 reports/build.txt。把它变成绝对路径后得到工作区内对象,功能与策略恰好一致。若输入是 ../../etc/hosts,同一个转换会得到确定的 /srv/etc/hosts 或其他外部地址;程序反而更容易成功打开。规范化对两种候选一视同仁,因为它没有被告知哪一棵目录树代表授权。

字符串前缀也不能替代目录关系。允许根 /srv/work 与兄弟目录 /srv/work-backup 共享文本前缀,简单的 startsWith 会把后者误判为内部对象。Windows 路径还涉及大小写、分隔符、盘符、UNC、目录联接点与重解析点。正确比较需要先按平台语义解析,再以路径 component 判断祖先关系;原始字符串的相似字节不能证明目录归属。

修复中的 Path.resolve() 负责得到规范化后的候选路径和根目录,Path.is_relative_to() 再检查前者能否以路径组件相对后者表示。这个顺序很重要:先比较再解析,链接和父目录仍能在比较通过后把目标带到外部;只解析不比较,程序只是获得一个更准确的外部地址。两步合起来才形成可解释的根目录归属规则。

expanduser 一类便利语义同样需要被纳入策略。若产品允许 ~,它会把短写扩展到服务账号的主目录,而那里往往正是凭证与配置聚集处。安全接口可以选择不接受这种表示,或在扩展后照常检查根目录归属;关键不是禁止某个字符,而是确保任何合法语法最终都只能落在同一个解析后的根目录内。

路径比较还需尊重“名字”与“对象”的差异。硬链接可以让同一文件内容拥有多个目录项,挂载可以把另一个文件系统接到根内;规范化路径主要解决父目录跳转与符号链接,不会替组织判断根内每个对象的来源。将允许目录设成只包含复制后产物的暂存区,会比在大型仓库上继续叠加名称规则更可审计。

对于不存在的候选,strict=False 可以完成组件归属判断,但上传随后仍应失败于文件不存在。测试应确认这种失败不会被误记成安全拒绝,也不会触发自动创建空文件。策略层与功能层拥有不同错误语义,保持两者分离有助于未来修改文件打开模式时继续守住“上传只读现有对象”的预期。

手绘图把两条看似整理后的路径并排放置,一条仍在工作区围栏内,另一条虽然格式规范却越过围栏,说明路径规范化与目录归属是两个判断。
图 3|规范化只消除“怎么写”的差异;解析后的候选是否仍位于允许根目录,必须由独立的目录归属检查回答。

3.2 绝对路径、父目录与符号链接只是同一次逃逸的不同外观

绝对路径是最直接的外部选择。Linux 上可以指向环境伪文件、用户主目录下的配置、容器挂载或系统文件;Windows 服务则可能访问其他盘符、网络共享或用户配置目录。操作系统仍会执行 ACL,因此并非所有目标都可读,但漏洞不需要绕过 ACL:只要存在一项对服务账号合法、对本次工具调用不应发布的对象,授权错位就成立。

父目录跳转更容易混入看似正常的相对参数。例如工作区内某个深层输出目录接收 ../../../secrets/example,如果调用者或模型依据文件名猜测路径,字符串表面仍像项目引用。仅删除一个 ../ 片段既无法处理重复、编码与平台分隔符,也可能破坏合法名字。统一解析后比较,才能让层级数量和书写方式失去绕过价值。

符号链接展示了为什么“候选字符串位于工作区”仍不够。workspace/export/latest 每个文本组件都在根目录之下,但 latest 可以指向外部凭证文件或目录。规范化解析会跟随已经存在的链接,最终目标因而落在根外并被拒绝。指向根内的合法链接则可以继续工作,避免用一刀切方式破坏正常构建产物组织。

目录链接还会放大一次误配。若管理员把大型共享卷挂到工作区子目录,规范化后的路径仍会被视为根内,因为挂载点本来就是允许树的一部分。代码无法替部署者判断整个挂载是否都适合发布;根目录限制是否有意义,取决于这个根是否足够窄。修复负责检查归属,运维负责决定根内放哪些数据。

测试矩阵需要同时包含成功与失败样本。根内普通文件应上传,根内合法链接应按产品预期处理;外部绝对路径、兄弟前缀目录、嵌套路径穿越、指向外部的链接以及不同盘符必须在 open() 前拒绝。只运行恶意样本会漏掉兼容性退化,只运行正常样本又无法证明安全属性,两组结果共同构成发布验收。

链接测试要在支持的平台上记录前置条件。Windows 创建符号链接可能需要特权,CI 因权限跳过用例时不能仍把套件标为完整通过;可以使用目录联接点或受支持的重解析点测试夹具补测,并在报告里明确覆盖差异。Linux 容器测试则需确认链接目标确实位于不同目录或挂载,避免测试夹具意外仍在根目录内。

大小写与 Unicode 也会影响某些文件系统的名称等价关系。安全逻辑应依赖 pathlib 和平台实际解析,测试用例采用真实临时目录,避免自行把字符串转成小写或做不完整编码归一化。上游支持哪些平台,就在哪些平台执行路径矩阵;未测试组合保持显式限制,不能从 POSIX 结果推断所有 Windows 配置。

三条手绘越界路径分别表现绝对外部路径、父目录跳转和符号链接指向外部,三条都在允许根目录边界被红色阻断。
图 4|绝对地址、.. 与链接不是三种独立根因;它们都试图让最终解析后的目标落到允许根目录之外。

3.3 当前工作目录成为安全根以后,启动方式也进入威胁模型

validate_safe_path() 的默认基准目录是 Path.cwd()。这项选择让上游无需引入新配置就能保持现有工作流,但也改变了当前工作目录的意义。过去它只影响相对路径从哪里开始;现在它定义工具获准读取的整棵树。若服务从 /、用户主目录或包含多个仓库的父目录启动,补丁虽在运行,允许范围仍然过大。

systemd 部署应显式设置 WorkingDirectory,并让服务账号只读取一个专用产物目录。容器既要设置 WORKDIR,也要检查挂载:如果先把密钥、Docker 套接字、云服务凭证和整个宿主目录挂进容器,再精确设置工作目录,可读范围依然过大。桌面启动器也不应继承不可预测的目录,最好为每个配置建立独立的暂存区。

Windows 服务需要在真实服务身份下验收。交互式 PowerShell 测试的当前目录、盘符映射和用户主目录可能与 Service Control Manager 启动时完全不同;目录联接点、重解析点与 UNC 路径的行为也应纳入实际平台测试。启动遥测可以记录解析后的根目录、软件包版本和进程身份,但不应输出目录内密钥或完整环境。

规范化解析与 open() 仍是两个文件系统操作。如果不可信的本地用户能在校验后、打开前替换链接或路径组件,就存在竞争窗口。mcp-atlassian 的公开修复重点是远程参数逃出工作目录,并不声称解决所有本地竞争。部署时应由服务所有者控制产物目录及其父目录,只以受控方式放入生成物,不要把目录与能够任意改动链接的本地用户共享。

由此可见,补丁不是把安全责任全部交给一个函数,而是建立了一份更清晰的运行契约:代码保证解析后不离开基准目录,启动配置保证基准目录足够小,操作系统权限保证根目录内对象按预期可读,本地目录所有权保证校验到打开之间的假设稳定。四层都能被单独测试,问题也终于能被定位到具体层级。

4 不可信内容只负责点火,真正决定损失的是服务端能打开什么

公告的复现从 Jira 内容开始,这使人自然把注意力放到提示注入。这个入口确实重要:模型读取业务对象后,可能把其中的指令带入规划,并以用户没有逐项核对的参数调用高影响工具。不过,提示注入描述的是调用如何被激活,文件范围缺口描述的是调用被激活后能够到达哪里。二者需要分开测量,也需要分别治理。

若只调整提示词、换模型或增加确认窗口,攻击概率可能下降,服务端的本地权限上限却没有改变。另一个客户端、直接工具调用、被盗会话或内部自动化仍可提交相同路径。服务端在每次访问前检查根目录归属后,即使模型再次选择外部文件,结果也会稳定地停在策略拒绝。客户端控制负责减少危险意图,服务端控制负责限制任何意图的最大后果。

4.1 从 Jira 文本到 Confluence 附件,激活链跨过四个信任域

第一段是内容域。Jira 事项、评论或描述由组织成员、外部协作者、邮件自动化或其他集成写入,客户端取回后通常把它们当作任务上下文。显示在项目系统中不等于经过工具指令审核;内容来源、作者权限和变更历史应当伴随文本进入智能体上下文,避免“可读取”被误解为“可指挥”。

第二段是规划域。模型根据系统指令、用户目标、检索内容与可用工具生成调用。不同模型、客户端、确认策略和上下文排列会改变复现稳定性,因此公告中的端到端演示不能被简单外推为所有部署都自动利用。它证明了一条现实可达路径,同时把每个组织需要自行核对的激活条件暴露出来。

第三段是工具域。MCP 客户端把结构化调用发送给 mcp-atlassian,服务端根据参数结构接收内容 ID 与路径。此时,模型输出已经变成确定参数;后续代码不再知道哪一段 Jira 文本影响了选择,除非客户端保存来源记录。若工具调用日志只记录“upload_attachment 成功”,调查便会失去内容与参数之间最关键的连接。

第四段是数据与发布域。进程打开本地对象,集成身份创建远端附件。两端操作分别进入主机和 Atlassian 的审计体系,时间、身份与对象名称都可能改变。激活链的最后结果由远端权限固化,随后可能被搜索、通知、下载和备份。一次模型决策因此跨越了内容治理、工具治理、主机数据与云端协作四套控制。

防守设计应让这四段都留下最小充分证据:来源内容的稳定 ID 与版本、模型会话和用户、工具名及参数策略结果、本地文件相对根目录的对象、远端附件 ID。日志无需保存密钥字节,也无需完整复制提示词;只要关联键稳定,调查者就能证明是哪项内容影响了哪次调用、调用又产生了哪个持久对象。

客户端应把来源对象 ID、作者和信任级别作为结构化上下文保留下来,并在执行前显示根目录内相对路径、目标产品与页面、集成身份及触发调用的内容来源;工具策略可以据此要求更强确认或禁止自动写入。超出根目录的候选仍由服务端直接拒绝,不能交给一次容易误点的确认来授权。这样,客户端负责说明意图,服务端负责限制结果,两层控制不会因模型或界面变化而彼此失效。

4.2 暴露上限由本地可读集合与远端可见集合共同决定

资产评估从运行进程开始。记录软件包版本、Python 环境、启动命令、当前工作目录、操作系统账号、容器镜像、挂载、环境注入和启用工具。桌面实例、团队共享服务端、CI 任务与开发容器可能使用不同目录和身份,不能用一台测试机的结果代表全部部署。每个实例都要形成独立的“可读根—远端身份”映射。

本地可读对象通常包括源码、构建产物、dotenv、云凭证缓存、SSH 材料、包管理器令牌、应用配置、日志和挂载的服务账号令牌。Linux 的 /proc/self/environ 在许多部署中会暴露启动环境,但具体内容取决于进程与平台。盘点应验证服务身份实际能读取什么,不采用一份通用“敏感文件路径表”替代现场证据。

远端一侧要枚举集成身份可创建附件的 Confluence 空间、页面权限、访客与群组继承。Jira 附件路径也因修复共用同一验证函数,部署若启用了相关工具,应把项目、事项安全方案与附件访问纳入范围。潜在披露并不限于公告演示的 Confluence 页面,实际目的端由每套配置和调用参数决定。

两组集合相交后,还要考虑内容生命周期。某个令牌即使只被一名管理员看到,也可能需要轮换;普通源码即使被广泛下载,商业影响可能由仓库公开程度决定。附件版本、页面导出、索引、邮件通知和备份让删除动作无法立即覆盖所有副本。风险排序应同时看数据敏感度、查看者、可撤销性与留存路径。

前置条件也要写清。攻击者或受污染内容必须影响工具调用与 file_path;mcp-atlassian 版本低于 0.22.0;进程必须读取目标;集成身份必须在远端创建附件。任何一项不成立都会阻断这条具体路径。清晰列出条件可以帮助团队快速排除不受影响实例,也防止把“安装了 MCP”直接等同于“已经发生数据泄露”。

优先级可以按实例计算。高优先实例通常同时具备共享调用入口、过宽的当前工作目录或敏感挂载、长期远端令牌、外部协作者可见空间和较弱的工具确认;低优先实例可能是单用户、专用空暂存区、只读远端身份或从未启用附件工具。分层结果必须由配置证据支撑,不能仅凭“开发环境”标签自动降级。

4.3 安全复现只需要两个测试标记和一条负向断言

验证不需要使用真实凭证或系统敏感文件。在隔离测试目录中创建一个受控根目录,把普通测试标记放在根内;再在同级目录放置第二个无害标记。受影响版本会接受两条可读路径,修复版本应只上传内部对象,并在本地文件打开与远端请求发生前拒绝外部对象。两份内容都使用明显测试字符串,测试后清理远端附件。

父目录用例可以从根内构造相对候选指向外部测试标记;绝对路径用例直接引用同一文件;链接用例在根内建立符号链接,目标仍是外部标记。三者应得到一致拒绝。另建一个指向根内标记的链接用于兼容性确认。测试记录候选类别、与解析后根目录的关系、是否调用直接上传函数、是否出现出站请求与是否生成附件 ID。

负向断言的重点是拒绝位置。若服务端先打开文件、读入内容,随后才因远端权限失败,本地文件范围仍未受控;若远端创建空附件后再回滚,也会留下审计与版本噪声。测试应替换或观察 _upload_attachment_direct(),证明越出根目录的路径没有抵达该函数,并确认 Atlassian 端没有相应对象。

端到端智能体测试可以作为第二层。使用自有 Jira 测试项目放置明确的测试指令,让客户端在人工监控下运行,观察内容来源、确认界面和服务端拒绝是否都按预期出现。这个实验测量的是激活控制;测试标记的拒绝矩阵测量的是服务端权限上限。两者结果分开记录,模型行为变化不会掩盖文件策略退化。

最后加入升级后的正常业务回归:从专用产物根目录上传文档、图像与批量文件,确认评论、minor_edit、同名版本和错误提示保持可用。安全修复只有在拒绝外部路径与保留合法流程两方面都可重复,才适合进入共享服务。这样,复现从展示危险转为验证一条明确不变量:未经工作区解析器认可的路径永远不能形成远端附件。

测试环境的远端身份应只写入专门页面,且不与生产通知、搜索或自动化连接。每次运行记录创建对象并主动清理,测试标记不包含任何真实密钥。这样即使外部路径用例意外在旧版本上成功,影响仍限制在自有无害数据,团队也能保留完整请求与附件证据用于比较。

回归失败时,先判断属于策略、平台或测试夹具。外部测试标记被成功上传是安全失败;根内标记因页面权限失败属于远端配置;符号链接用例未创建则是测试前置条件。明确分类可以防止团队用网络错误掩盖路径规则退化,也防止把普通业务故障误报成漏洞仍可利用。

5 修复先解析真实目标,再决定它是否有资格被打开

提交 b041733473f95119dd539542a43c280737a8e460 没有重写整个附件栈。它把既有的共享路径验证能力接入 Confluence 与 Jira 本地上传入口。改动规模不大,安全含义却很明确:候选路径在进入业务上传逻辑前,先转换成相对于可信根目录的规范化对象;无法证明归属的候选直接失败。

阅读补丁时,需要把通用函数、调用点和测试一起看。通用函数给出算法,调用点证明算法覆盖实际敏感操作点,测试固定未来修改不得破坏的行为。如果只看到 validate_safe_path() 存在,仍不能确认上传函数使用了它;如果只看到调用,又不核对基准目录、解析与比较语义,仍可能把一个名称漂亮的薄包装误判为完整修复。

5.1 validate_safe_path() 把基准目录与候选路径放进同一坐标系

验证函数位于 src/mcp_atlassian/utils/io.py。函数接收候选路径与可选 base_dir;未提供基准目录时使用 Path.cwd()。它先展开并解析基准目录,确保比较对象本身已经规范。相对候选路径与基准目录连接,绝对候选路径保持自己的起点,随后同样执行解析,使两者进入同一套平台路径语义。

候选路径使用 strict=False 解析。这个选项允许路径末端尚不存在时仍得到规范地址,之后的业务逻辑或 open() 继续负责存在性与可读性。对上传来说,目标通常应当已经存在;安全要点并不依赖 strict 的取值,而是解析完成后必须比较归属,并且越界候选不得进入文件读取。

比较由 resolved_path.is_relative_to(resolved_base) 完成。它按照路径组件判断候选路径是否以基准目录为祖先,避免文本前缀相似的兄弟目录被接受。失败时验证函数抛出异常;成功时返回解析后的路径。调用者使用返回值替换原始输入,从而保证后续文件名提取、存在性检查和 open() 围绕同一个已经验证的对象运行。

这项顺序消除了“检查一个字符串,打开另一个字符串”的常见偏差。若验证只返回布尔值,后续代码仍可能继续使用原始相对路径,而当前工作目录变化或二次处理会改变目标。返回规范化路径让策略结果成为后续 I/O 的唯一输入,也使测试能够断言直接上传函数收到的就是解析后的安全地址。

验证函数接受可选基准目录,说明代码库具备使用更窄根目录的能力;当前附件调用点没有传入自定义基准目录,因此部署生效值仍是运行时当前工作目录。包装器若将来增加独立上传区,必须确保 Confluence 单文件、批量、Jira 和任何新附件入口都传递相同策略,并为配置缺失、路径变化与多实例隔离补齐回归。

验证函数的单元测试最好用属性思维补强:任意被接受的候选路径在解析后都必须位于同一基准目录内,任意已知外部目标无论采用绝对、相对或链接表示都必须拒绝。固定示例能够守住已知回归,属性则约束未来重构不会因新增路径语法重新打开缺口。

手绘验证工作台依次解析工作区根和候选路径,比较最终目录归属,通过的文件进入上传台,越界文件在创建句柄前被挡下。
图 5|修复的先后顺序是安全属性本身:解析根目录、解析候选路径、比较目录组件、返回规范化路径,最后才允许附件函数打开文件。

5.2 各产品的 upload_attachment() 入口是足够窄的控制点

Confluence 的 AttachmentsMixin.upload_attachment() 现在先执行 validate_safe_path(file_path),把结果转换成后续代码需要的字符串,再提取文件名并调用直接上传辅助函数。拒绝发生在文件句柄和 HTTP 请求之前。这个位置保留了业务层可解释错误,也覆盖通过获取组件、批量工具或直接库调用进入的路径。

Jira 附件辅助函数同样接入通用函数。这一变化值得单独核对,因为公告主要用 Confluence 演示,根因却属于服务端的本地附件选择。若只修演示入口,另一个产品工具仍可能复用同样过大的能力。共享验证函数让两个远端产品得到一致的本地文件系统约定,也减少未来维护时出现语义分叉。

控制点不能放得太晚。若在 _upload_attachment_direct() 已经构造文件对象后才校验,敏感内容可能进入内存、日志或重试缓存;若只依赖远端接口,Confluence 与 Jira 根本看不到可信根目录。控制点也不能放得太早且只覆盖一个界面封装程序。各产品的附件入口位于业务调用和 I/O 读取之间,既掌握完整路径,又足够接近所有打开动作。

Base64 内容分支应保持不同的审计语义。它不读取服务端文件系统,因此不需要把请求内容伪装成一个临时路径再经过验证函数;那样会扩大磁盘副本与清理面。相应地,服务端应对内容大小、调用者权限、日志脱敏和远端目标执行限制。两种模式共享“谁能发布到哪里”,只有 file_path 模式额外承担“服务端能从哪里取字节”。

未来新增上传入口时,代码评审应从敏感操作点反向枚举调用者。搜索 open(..., "rb")、分段表单的文件字段、Jira/Confluence 附件方法与批量便捷函数,确认任何主机路径都先经过同一路径解析器。仅搜索工具名称会漏掉库调用者,搜索验证函数名称又可能漏掉新建的未保护敏感操作点;双向遍历才能证明覆盖。

动态观测可以验证静态枚举。测试时在直接上传函数或文件打开层设置探针,依次触发所有公开附件工具,记录每次到达读取位置前是否出现策略事件。静态调用图说明应该怎样,运行轨迹说明测试实际覆盖了哪些入口;二者结合能发现反射注册、条件分支或封装程序带来的遗漏。

5.3 0.22.0 是修复下限,提交级证据决定下游是否等价

上游将 0.22.0 标为首个修复版本,相关提交同时存在于 v0.22.0v0.22.1 与后续发布。直接使用 PyPI 或上游镜像的组织可以把 0.22.0 作为最低线。发行版、内部下游分支与供应商包装若使用不同版本号,则需核对提交或等价实现,不能依赖字符串大小比较。

升级验收先确认进程实际加载的新代码。记录包管理器输出、模块文件路径、镜像摘要和进程重启时间;在运行环境中检查 Confluence 与 Jira 上传函数都引用验证函数。热重载、长期驻留工作进程或桌面客户端后台进程可能继续运行旧模块,安装成功不代表现有会话已切换。

上游通用函数测试覆盖根目录内相对与绝对路径、外部绝对路径、父目录和嵌套路径穿越;附件测试断言防护检查位于直接上传辅助函数前。下游应补充自己支持的平台与启动形态,包括符号链接、兄弟前缀、Windows 盘符和目录联接点、容器挂载、批量工具,以及当前目录由启动器设置的真实值。

补丁不会自动缩小根目录内的权限。若当前工作目录本身包含 dotenv、源码、输出、凭证和套接字,所有位于树内且可读的文件仍满足归属检查。升级工单因此要同时包含目录重构:创建专用暂存根目录,移动允许上传的产物,排除密钥和控制接口,再以服务身份运行根内与根外测试。只更新软件包会留下过宽但符合新规则的范围。

补丁也无法撤回已经创建的附件。版本修复负责阻止新的外部路径读取,历史处置仍需检查工具日志、主机事件和远端副本。把版本时间、进程重启和最后一条受影响调用放在同一时间线,可以明确调查窗口的结束点;若证据不足,窗口应延伸到外部路径拒绝测试首次通过且旧进程全部退出的时刻。

软件物料清单应记录 mcp-atlassian 的直接与传递来源。桌面应用可能把它打包进自身运行时,容器可能在构建阶段安装,内部工具也可能以 Git 提交固定。扫描只在顶层 requirements 查找包名会漏掉这些形态;从运行进程反向获取模块位置,再映射到镜像和构建记录,覆盖更可靠。

下游回移需要保留来源、补丁内容、测试结果和重新构建摘要。若只复制通用函数却漏掉 Jira 或批量调用点,版本看似修复,能力仍不等价。发布说明中列出已保护入口与生效基准目录,使资产团队能够按功能验证,不必从一个私有版本号猜测安全状态。

手绘发布轨迹从存在裂缝的旧软件包经过验证器修复台,抵达封装完整的新版本,并提示下游回移需要提交级核对。
图 6|版本号给出上游修复下限;镜像、下游分支与回移包还需用提交、调用点和外部路径拒绝测试证明它们执行了等价控制。

6 主机与云端各保存一半事实,只有关联以后才像一场事件

mcp-atlassian 的披露路径横跨内容系统、智能体、工具服务、文件系统、网络与 Atlassian。任何单一日志都只看见一段:Jira 看到文本,客户端看到调用,主机看到读取,代理看到 HTTPS,Confluence 看到附件。高质量调查的任务不是收集越多截图越好,而是让这些记录围绕同一对象、同一进程和同一校正时间彼此解释。

最稳定的锚点通常来自工具请求与附件 ID。前者靠近参数来源,后者代表持久结果;中间再用进程、容器、目标主机、字节大小与时间连接文件打开和出站请求。若产品没有端到端关联 ID,调查可以建立自己的事件键,并为每条关联标明直接观测、强关联或分析推断,避免把相邻时间自动写成已证实因果。

6.1 一条可复算时间线至少需要七类对象

第一类是来源内容:Jira 事项或 Confluence 页面的稳定 ID、版本、作者、修改时间和客户端读取时间。保存原始对象应遵循事件证据流程,公开报告只引用必要片段。重点是证明某个会话当时看到了什么,不需要把整段不可信指令复制到所有工单,也不需要依赖模型事后解释自己的决策。

第二类是会话与工具调用:用户、客户端实例、模型会话、工具名、请求 ID、内容 ID、原始参数或脱敏路径、确认结果、返回状态和时间。若原始路径含用户名、项目名或密钥位置,应限制这部分日志的访问。修复后还应记录受控根目录的标识、目标是否位于根内以及拒绝原因,使未来调查无需打开敏感日志正文,也能先完成聚合。

第三类是主机对象:进程 PID、启动时间、可执行与模块版本、当前工作目录、容器 ID、文件路径、inode 或 Windows 文件标识、打开模式和读取字节。文件随后可能变化或被删除,哈希只在合法取证流程中计算;对于环境伪文件,内容随进程而异,记录进程身份与采集时刻比笼统文件名更关键。

第四至第七类分别是出站请求、远端附件、查看或下载记录,以及凭证后续使用。网络记录提供目标域、连接时间、字节和代理身份;附件记录提供页面、版本与权限;访问记录说明扩散;凭证日志说明披露是否继续转化为账号活动。每类证据都有保留期限和攻击者可修改程度,案件板应明确来源完整性。

关联完成后,时间线应能回答四个问题:哪段内容或哪位用户触发调用,服务端打开了哪个本地对象,字节落成哪个远端附件,谁在何时能够接触结果。无法回答的空白继续保留为空白,并转化为下一项采集任务。这样,结论可以随新证据升级,也不会因早期过度确定而在后续修订中失去可信度。

每个数据源都要标注覆盖窗口。应用日志可能在容器重启后丢失,audit 规则可能只监控特定目录,Atlassian 套餐可能不提供逐次下载,代理也可能只保留域名和字节。未命中只有在来源完整覆盖事件窗口且攻击者难以修改时才具有较强排除力;缺失来源不能被写成“没有发生”。

时钟校正应保存过程。记录主机、容器、代理和 Atlassian 时间格式,使用已知请求或 NTP 状态估计偏差,把原始时间与校正 UTC 同时留存。若只能关联到一个范围,就在时间线上画出窗口,不伪造精确秒值。对并发上传环境,文件大小、内容 ID 与进程生命周期可帮助区分相邻请求。

调查桌上摆放来源内容、工具调用、主机文件事件、网络请求、远端附件与查看记录,多条红线围绕同一时钟和请求对象汇合。
图 7|来源内容解释动机,工具调用保存参数,文件打开证明读取,附件 ID 固定结果;四段证据必须共享可说明的时间与身份关系。

6.2 远端处置要覆盖附件版本、访问者与派生副本

修改可疑附件前,先保存附件 ID、内容 ID、filename、版本、创建者、时间、大小、页面权限和可用审计。存在持续暴露风险时可以立即限制页面或附件访问,同时通过受控取证路径保全证据。直接下载文件进行“看看里面是什么”可能扩大接触者名单,敏感内容应由被授权的最小团队处理。

远端生命周期需要逐项核对。旧附件版本是否仍可取回,页面历史和导出是否包含字节,搜索索引是否建立摘要,通知或 automation 是否把文件发送到其他系统,备份何时到期。删除当前版本并不会自动清除这些派生对象。每一处副本应记录责任人、处置状态、验证方法与预计销毁日期。

若内容包含令牌、私钥、密码或会话材料,轮换优先于等待完整查看者清单。从可信终端撤销旧凭证,替换下游密钥,终止派生会话,并监控旧值的失败使用。轮换本身也会改变系统状态,时间与操作者需要进入案件记录,以便区分攻击活动和响应动作。

源码、配置和业务数据的处置方式不同。仓库密钥可能还存在历史提交与构建缓存,配置可能暴露内部地址或账号结构,客户数据则牵涉通知与监管义务。事件负责人应把“文件曾被上传”翻译成每一类数据的后续动作,避免用统一的删除工单替代数据所有者判断。

结案时,主机侧应证明越界路径已被拒绝,云端侧应证明已知副本受控,身份侧应证明敏感凭据已轮换,治理侧应记录仍受备份保留约束的对象。任何一项暂时无法完成,都作为带期限和责任人的剩余风险保留。这样的结案标准比“页面上已经看不到附件”更接近真实披露生命周期。

通知范围由数据所有者与法律团队共同决定。技术团队提供文件身份、内容类别、接收者和保留状态,业务负责人判断客户、员工或合作方影响。若内容尚未获准查看,可先依据路径、创建来源和大小进行保守分类,再由最小授权小组确认;调查效率不要求无限扩大敏感数据访问。

远端管理员的动作也应可审计。限制页面、删除附件、清理版本或导出证据时,记录工单、操作者、UTC 和操作前后状态。这样,后续出现缺失对象时可以解释是响应处置还是攻击者清理,也能证明组织在发现后何时停止了继续访问。

6.3 处置顺序既要停止新外传,也要保存已经发生过的状态

  1. 定位全部实例并保存易失证据。盘点桌面实例、容器、共享服务和 CI 中的版本、进程、当前工作目录、挂载、工具与远端身份;导出客户端与服务端日志、进程信息、相关代理记录和 Atlassian 元数据。若业务允许,先暂停本地附件工具或限制集成身份写入,减少新调用,同时避免立即删除进程与容器造成日志丢失。
  2. 升级并重启。安装 0.22.0 或更新版本,核对加载模块已经包含修复,确保所有旧工作进程退出。在专用产物目录下启动,以无害测试文件执行根内、绝对外部路径、父目录穿越与符号链接测试。每个外部路径用例都必须在直接上传函数之前停止,远端不得出现附件 ID,而正常用例仍应成功。
  3. 开展历史调查并限制远端对象。以最后一个受影响进程退出为窗口终点,向前覆盖旧版本部署期和日志可用期;关联来源内容、工具调用、文件打开、出站请求和附件。发现疑似敏感副本时,保存元数据后限制访问,通知数据责任人,并按内容类型启动凭证轮换、隐私评估或客户沟通。
  4. 收窄长期能力。为附件建立服务专用暂存区目录,移出 dotenv、凭证、套接字与无关仓库;拆分只读与可写工具配置,对本地文件加远端写入的组合要求明确确认。客户端显示来源内容与参数,服务端持续执行路径策略,两层控制共同降低误触和滥用。
  5. 让验收可以重复。启动时记录解析后的根目录与软件构建信息,用聚焦路径矩阵核对更新后的行为,生产监控聚合外部路径拒绝和异常附件创建,并定期复核 Atlassian 身份权限。实际可读范围会随启动目录、挂载、封装程序和新工具改变,后续变更必须重新证明受控目录没有悄悄扩大。

业务连续性方案要预先准备。若附件工具无法立即升级,可以临时从工具配置移除、撤销远端写入、将进程迁入空暂存区或在外层阻断调用;选择哪项取决于业务依赖。每个缓解措施都要有验证和到期时间,并明确它没有处理历史副本,防止临时控制被误写成完整修复。

桌面用户与共享服务的沟通内容也不同。个人实例需要清晰的升级、重启和工作目录指引;共享服务需要变更窗口、证据保全、凭证轮换与调用者通知。统一公告保留 GHSA、版本下限和测试文件的验证方法,不向普通用户传播真实敏感路径或未经确认的泄露清单。

响应工作台按顺序摆放证据封存、实例升级、工作区收窄、远端附件处置、凭证轮换与回归测试工具,表现业务连续性和取证并行推进。
图 8|先保存易失证据并停止新外传,再升级、收窄根目录、处理远端副本和轮换凭证;最后用负向测试证明旧路径已经失效。

7 受管产物把能力演进与事件闭环落到同一个对象上

漏洞表面上只少了一道目录限制,背后组合的却是三种本来都合理的能力:不可信内容可以影响规划,工具可以借主机权限取得字节,长期云身份可以把字节持久发布。修复后的不变量依然简单:候选路径解析后的目标必须位于解析后的工作区根目录之下,判断通过前不得创建文件句柄。

继续围绕自由路径堆治理清单,不会让这项授权更清楚。更自然的演进,是把待发布内容表示成一项短期能力,让来源、所有者、作用域与目的端在真正使用时重新验证。兼容路径仍可保留,新工作流则获得一个携带授权状态的业务对象。

回到开场那份附件被模型选中的一刻。自由路径交给服务端的只有一个地址,相关事实都得临时重建;在这段时间里,文件可能被替换,软链接可能改指别处,也可能把另一租户的产物误认成当前任务的输出。受管产物表达的则是一项已经封存的陈述:这些字节由这个工作流在这个根目录内生成,只能发往这一类目的端,而且许可会过期。附件动作消费的是这项陈述,不再把地址重新解释成权限。

7.1 用产物身份替代调用者拼出的主机地址

生成工具把输出放入服务方控制的暂存区,并登记对象 ID、相对根目录路径、大小、类型、创建者和过期时间。附件工具接收对象 ID,在打开前再次核对对象状态。迁移期内,已经通过目录验证的 file_path 可以转换成同一种内部对象,避免兼容模式形成第二套安全语义。

登记产物必须经过一次状态转换,给现有路径补贴一条数据库记录并不够。生成端先写入私有临时位置,服务端在接收数据流时核算大小、判断获准媒体类型并计算摘要;只有原子移动进暂存区后,对象才从“写入中”变成“已封存”。不可变清单把最终大小与摘要同创建者、过期时间放在一起,半截写入或事后替换都不能悄悄继承已封存对象的权限。

这项能力与创建它的会话或工作流绑定,也与获准目的端绑定;跨页面、跨产品或跨租户发布需要重新授权,不能继承第一次许可。产物准入还有一条不可退让的条件:其他工具不能把任意主机文件复制进暂存区,否则没有收窄能力,只是把无界选择提前了一步。

因此,授权结论不是贴在文件名上的一个布尔值,而是一组必须同时吻合的条件:发布身份、操作、产物 ID、目标租户与容器、有效时间窗。给使用者看的确认界面也从同一组信息生成,明确展示名称、字节数、来源、目的端以及实际使用的云身份,让界面上的同意与服务端执行的是同一个决定。同一对象 ID 若被重放到另一页面或事项,即使字节没有变化,也会因为目的端不同而被拒绝。

受管对象同时定义撤销、过期与重试。任务完成、取消或超时后,对象 ID 失效;重试只能发生在有效期内。若确有一个暂存区外对象需要发布,由受控任务把这一项复制到隔离区,记录来源、哈希、审批与过期时间。一次例外只扩大一个对象,不把 cwd 或整棵主机目录永久开放给后续调用。

使用时复核还要关掉“检查路径”和“真正打开”之间的竞态窗口。服务端可以在受控目录内取得句柄,确认句柄仍指向封存对象,再从同一句柄流式发送并执行已登记的大小限制;无法提供这些保证的环境,则只复制一次到隔离暂存区,后续只发布副本。这也解释了为什么规范化路径是当前代码不可缺少的修复,却仍不是可转移能力的最终形态。

上传以“产物、目的端、操作”组成的幂等记录管理后,重试也更容易说清。网络超时可以查询或继续同一次尝试,不必扩大权限;换一个目的端则必须重新决策。对象签发、封存、授权、打开、出站请求、远端附件 ID、消费、撤销与过期事件串成一段紧凑历史。它不保存可复用的文件内容,却足以把主机上的选择和 Atlassian 最终保存的对象连接起来。

7.2 只有把可能性与实际发生分开,开场那份附件才真正闭环

修复前,一次成功上传只能证明进程读到了对象、集成身份能写页面、Atlassian 接受了分段请求。修复后,同一成功多了一项可验证前提:规范化来源位于收窄后的暂存根目录,直接上传函数收到的是通过验证的对象。受管产物把这项前提直接写进对象,不必等事件发生后再从字符串倒推。

这一区分会直接改变调查队列。运行脆弱版本并挂载了宽目录的实例,因为可达集合很大,应立即修复和收敛,但在运行证据指向具体候选对象前,它仍是一项暴露事实。工具调用若出现工作区外路径,优先级会再升高,却依然不能证明打开或上传成功。只有把解析后的对象、成功读取、分段请求、远端响应和附件 ID 连成一线,才足以得出泄露结论。遥测缺失会降低置信度,不会让推测自动变成事实。

结案同样要让各端分别给出结果:主机只运行修复后的进程,外部候选在直接上传函数前被拒绝;远端已控制已知附件版本、查看者、派生副本与备份期限;身份系统已经失效暴露材料;客户端保留来源,并在确认时展示产物、目的端与发布身份。四项验收汇合后,开场那份普通附件才终于能同时回答“字节去了哪里”和“它为什么有资格离开主机”。

研究记录

8证据、对象与来源

下面保留本文实际使用的标识、时间和原始材料,便于继续调查。

8.1研究对象

报告涉及的产品、行为者、技术、受影响对象和控制点。

公告GHSA-g5r6-gv6m-f5jv

mcp-atlassian 本地文件读取与远端附件上传

产品mcp-atlassian < 0.22.0

受影响范围;0.22.0 首次修复

函数confluence.upload_attachment(file_path)

FastMCP 附件工具入口

函数AttachmentsMixin._upload_attachment_direct

本地打开与分段上传控制点

控制点validate_safe_path / Path.is_relative_to

规范化路径的根目录归属检查

提交版本b041733473f95119dd539542a43c280737a8e460

进入 v0.22.0 的修复提交

8.2事件时间

  1. 安全公告公开

    GHSA-g5r6-gv6m-f5jv 公布影响范围、CVSS 7.7 与端到端提示注入演示。

  2. 修复提交进入上游

    提交 b041733 把既有路径验证函数接入 Confluence 与 Jira 上传入口。

  3. 0.22.0 发布

    上游把 0.22.0 标记为首个修复版本。

  4. SOSEC 完成源码重建

    SOSEC 固定受影响调用链、修复语义、平台部署条件、证据关联与响应顺序。

8.3来源与材料

  1. mcp-atlassian GHSA-g5r6-gv6m-f5jv 安全公告https://github.com/sooperset/mcp-atlassian/security/advisories/GHSA-g5r6-gv6m-f5jv
  2. 修复提交 b041733https://github.com/sooperset/mcp-atlassian/commit/b041733473f95119dd539542a43c280737a8e460
  3. 引入 validate_safe_path 的历史提交 52b9b09https://github.com/sooperset/mcp-atlassian/commit/52b9b09
  4. mcp-atlassian v0.22.0 发布https://github.com/sooperset/mcp-atlassian/releases/tag/v0.22.0
  5. v0.22.0 Confluence FastMCP 工具定义https://github.com/sooperset/mcp-atlassian/blob/v0.22.0/src/mcp_atlassian/servers/confluence.py
  6. Confluence AttachmentsMixinhttps://github.com/sooperset/mcp-atlassian/blob/v0.22.0/src/mcp_atlassian/confluence/attachments.py
  7. Jira 附件辅助函数https://github.com/sooperset/mcp-atlassian/blob/v0.22.0/src/mcp_atlassian/jira/attachments.py
  8. validate_safe_path 实现https://github.com/sooperset/mcp-atlassian/blob/v0.22.0/src/mcp_atlassian/utils/io.py
  9. 路径归属回归测试https://github.com/sooperset/mcp-atlassian/blob/v0.22.0/tests/unit/utils/test_io.py
  10. Model Context Protocol tools 规范https://modelcontextprotocol.io/specification/2025-06-18/server/tools
  11. Model Context Protocol 安全最佳实践https://modelcontextprotocol.io/specification/2025-06-18/basic/security_best_practices
  12. Python pathlib Path.resolve 文档https://docs.python.org/3/library/pathlib.html#pathlib.Path.resolve
  13. Python pathlib PurePath.is_relative_to 文档https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.is_relative_to
  14. Atlassian Confluence content 附件 APIhttps://developer.atlassian.com/cloud/confluence/rest/v1/api-group-content---attachments/
  15. Atlassian Jira 事项附件 APIhttps://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-attachments/
  16. CWE-22 Improper Limitation of a Pathname to a Restricted Directoryhttps://cwe.mitre.org/data/definitions/22.html
  17. CWE-73 External Control of File Name or Pathhttps://cwe.mitre.org/data/definitions/73.html
  18. NIST SP 800-61 Rev. 3 Incident Response Recommendationshttps://csrc.nist.gov/pubs/sp/800/61/r3/final