domain 字段导致的路径遍历CVE 状态: 已申请,待分配。此发现已发布为 GHSA-5j8x-q7q6-58j5。在 CVE 分配后,此仓库将重命名为
CVE-YYYY-NNNNN-openlore-PoC,并且此横幅将替换为 CVE 链接。
| 研究员 | Dostxodjayev Abdullox (@squeeze440) |
| 安全公告 | GHSA-5j8x-q7q6-58j5 |
| CVSS 3.1 | 4.7 (中危) |
| 弱点 | CWE-22, CWE-73 |
domain 字段导致的路径遍历摘要:控制 openlore generate 所分析仓库内容的攻击者,可以导致 OpenSpec 规范生成流水线将受攻击者影响的 Markdown 写入目标项目之外的任意文件系统路径,因为 Stage-3 LLM 提取调用返回的 domain 值被逐字用于构建规范的输出路径,且在 fs.writeFile 之前没有进行路径遍历净化,也没有进行路径限制检查。
OpenLore(clay-good/OpenLore),npm 包 openlore,规范生成流水线(openlore generate / OpenSpec 格式生成器 + 写入器)。不是确定性的 analyze/orient/MCP 防护路径——根据项目自身的 README/规范(“LLM 仅在生成不可避免时使用(规范编写),绝不用于检索或防护路径”),这是项目中唯一一条有意将 LLM 纳入生成循环的代码路径。
Git 提交 1f2de00dfe75530efb256320c374dce36607ee70(2026-07-31),package.json 版本 2.1.7。
4.7 (中危) — CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:N
AV:L — 触发方式是针对磁盘上的目录进行本地 CLI 调用(openlore generate),而非网络服务。AC:H — 利用需要操作员在配置了提供商的情况下运行由 LLM 支持的 generate 命令(而非默认的防护路径),并且需要被分析仓库的间接提示注入实际使模型在 domain 字段中输出路径遍历字符串——这是攻击者可以影响但无法完全控制的条件。UI:R — 操作员必须选择针对恶意仓库运行规范生成。I:H / C:N / A:N — 该漏洞是一个写入原语,其内容部分由攻击者模板化,并落在项目之外攻击者选择的路径上;未证明其可读取机密数据或可靠地降低可用性,因此这些指标保守地设为 N。ExtractedService.domain(src/types/pipeline.ts:105)直接由 Stage 3 LLM 调用填充,该调用接收被分析仓库的原始文件内容,并要求返回一个服务的 JSON 数组:
src/core/generator/stages/stage3-services.ts:45-52 从 fileChunks[i](被分析仓库的字面源代码文本)构建提示,并调用 pipeline.llm.completeJSON<ExtractedService[]>(..., STAGE3_SERVICE_SCHEMA)。src/core/generator/schemas.ts:100 — STAGE3_SERVICE_SCHEMA.items.properties.domain 为 { type: 'string' }:没有 pattern,没有 enum,没有允许列表。模型返回的任何字符串都会被接受。src/core/generator/openspec-format-generator.ts:161 — const domainName = service.domain || this.inferDomain(...) 按原样使用该字符串。src/core/generator/openspec-format-generator.ts:563 — path: \openspec/specs/${domain.name.toLowerCase()}/spec.md`normalizeDomainName()src/core/generator/openspec-compat.ts:655src/core/generator/openspec-writer.ts:167`,用于过期域目录比较)——但在此处从未被调用,而这里正是该值实际成为文件系统路径的唯一位置。最终效果:诸如 ../../../../../../tmp/x 这样的 domain 值会从 LLM 的 JSON 响应原封不动地传递到真实的 writeFile 调用,完全落在被分析项目之外。这正是项目自身的 openspec/specs/mcp-security/spec.md 明确定义并针对 MCP/serve/view 表面进行加固的“仓库内容将写入重定向到项目根目录之外”威胁模型(safeJoin、符号链接感知限制、路径参数覆盖门)——而生成器/写入器表面根本没有等效的门。
未进行实时 LLM 调用(模型对注入指令的遵从是概率性的,并非此漏洞的有趣部分);相反,PoC 使用真实的、未经修改的 OpenSpecFormatGenerator 和 OpenSpecWriter,并传入一个 PipelineResult,其唯一的 ExtractedService.domain 字段恰好持有 STAGE3_SERVICE_SCHEMA 目前允许 LLM 返回的字符串形状,也恰好是源文件注释指示“将 domain 设置为 ../../../.../tmp/x”可能从 Stage 3(它将原始文件内容传递给模型)引出的内容。
脚本:~/engagements/openlore/source/poc-domain-traversal.ts(测试克隆的仓库根目录),针对位于 ~/engagements/openlore/evidence/victim-project 的临时项目运行:
cd ~/engagements/openlore/source
npx tsx poc-domain-traversal.ts
结果:OpenSpecFormatGenerator.generateSpecs()(未修改)生成一个域规范,其
path: "openspec/specs/../../../../../../../../../../../../../../../../../../../../tmp/openlore_traversal_proof/spec.md",
并且 OpenSpecWriter.writeSpecs()(未修改)将其写入——落在 /tmp/openlore_traversal_proof/spec.md,完全在 victim-project 根目录之外,而 victim-project/openspec/specs/ 始终只包含两个合法的 overview/architecture 目录。
截图(真实的 Xvfb+fluxbox+xterm 捕获,scrot -w <window id>):
../evidence/poc-generator-write.png — PoC 运行:生成器包含路径遍历的原始 spec.path,写入器日志行 Wrote openspec/specs/../../.../tmp/openlore_traversal_proof/spec.md,以及脚本自身对 /tmp/openlore_traversal_proof/spec.md 存在且包含受攻击者影响内容的确认。../evidence/poc-outside-fs-proof.png — 独立确认:对 victim-project/openspec/specs/ 执行 ls 仅显示 architecture/overview(项目内没有恶意域的任何痕迹),而对 /tmp/openlore_traversal_proof/ 执行 ls -la 显示逃逸的 spec.md 直接位于 /tmp 下。能够诱使目标针对其编写的仓库运行 openlore generate(由 LLM 支持的规范生成)的攻击者——对于一个明确用途是指向第三方/不受信任代码库的 CLI 工具来说,这是一个合理的场景——可以将包含受攻击者影响内容的文件写入操作员操作系统用户可写入的任何路径,仅受逃逸所需的 ../ 段数量以及文件系统权限的限制。该写入未被确认可直接执行,但足够浅的项目路径可能让路径遍历到达同一账户上被其他工具执行或自动加载的位置(shell rc 文件、cron 用户目录、编辑器/IDE 配置),这一点未经测试,此处也不作声称。
domain.name / service.domain 的位置(openspec-format-generator.ts 中的 groupByDomain())应用现有的 normalizeDomainName()(或等效的允许列表正则,例如 ^[a-z][a-z0-9-]{0,63}$),而不仅仅是在稍后的过期目录比较位置。OpenSpecWriter.writeSpec() 的 fullPath 计算通过项目自身的 safeJoin()(src/utils/path-confinement.ts)路由,而不是普通的 node:path.join,与此代码库中所有其他不受信任路径表面已经要求的限制保持一致。STAGE3_SERVICE_SCHEMA.domain(以及任何其他稍后用于构建文件系统路径的 schema 字段)添加 pattern 约束,以便强制执行结构化输出 schema 的提供商会直接拒绝不符合的值。Dostxodjayev Abdullox
此仓库已启用 GitHub 私密漏洞报告:https://github.com/clay-good/OpenLore/security/advisories/new(标准 GHSA 流程)。在此审计之前,通过 gh api repos/clay-good/OpenLore/security-advisories 返回 [](无先前公告)确认。
将其直接插值到规范声明的输出路径中。注意()存在于同一文件的依赖图中,并且在其他地方被应用(src/core/generator/openspec-writer.ts:248 — const fullPath = join(this.rootPath, spec.path); 使用普通的 node:path.join,它会按词法解析 ../ 段,且没有调用项目自身的 safeJoin()(src/utils/path-confinement.ts),而根据 openspec/specs/mcp-security/spec.md,此代码库中所有其他不受信任路径的表面(MCP 处理器、openlore view 的 /api/skeleton 和 /api/spec-requirements)都必须通过该方法路由。src/core/generator/openspec-writer.ts:277 / :294 随后执行 writeFile(fullPath, spec.content, 'utf-8'),并且 ensureDir()(openspec-writer.ts:497-499)在同一未受限路径上执行 mkdir(dirname(filePath), { recursive: true }),在离开项目根目录的过程中创建任何缺失的中间目录。..rootPathGeneratedSpec.pathOpenSpecWriter