From b39da174737925d64cb0968f2663d0b7fc5fa804 Mon Sep 17 00:00:00 2001 From: Chenghao Date: Sun, 26 Jul 2026 14:26:46 -0400 Subject: [PATCH 1/2] =?UTF-8?q?=E9=9D=9E=E6=B1=A1=E7=82=B9=E7=B1=BB?= =?UTF-8?q?=E6=BC=8F=E6=B4=9E=E6=A0=87=E6=B3=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config_vuln_annotation.md | 84 +++++++++++++++++++++++++++++++++++++++ config_vuln_changes.csv | 14 +++++++ data/entries.jsonl | 8 ++-- data/reports.jsonl | 2 +- entries.fixed.jsonl | 4 ++ 5 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 config_vuln_annotation.md create mode 100644 config_vuln_changes.csv create mode 100644 entries.fixed.jsonl diff --git a/config_vuln_annotation.md b/config_vuln_annotation.md new file mode 100644 index 0000000..b042394 --- /dev/null +++ b/config_vuln_annotation.md @@ -0,0 +1,84 @@ +# 配置类/非污点类漏洞标注 + +## 1. 适用范围 + +适用于根因来自静态配置、危险默认值、权限声明或缺少安全控制,而不是不可信输入传播的漏洞,例如: + +- Dockerfile 未声明 `USER`,容器继承基础镜像的 root 用户; +- sudoers、IAM 或文件权限配置过宽; +- 安全选项被关闭或必要限制缺失; +- 第三方构建脚本以不必要的高权限执行。 + +这类问题可以使用现有schema 表达,不应把配置关系强行标注为污点流或调用链。 + +## 2. 字段标注原则 + +### `entry_point` + +选择引入危险配置状态的最早可定位声明: + +- 不安全默认值未覆盖:选择引入默认值的位置,例如 `FROM`; +- 显式危险配置:选择配置声明本身,例如 `NOPASSWD:ALL`; +- 缺少配置项:不能伪造不存在的代码节点,应锚定真实声明,并在 `desc` 中注明检查范围和缺失项。 + +### `critical_operation` + +选择核心缺陷或危险配置的生效位置: + +1. 有显式危险配置时,选择配置声明本身; +2. 缺少安全覆盖项时,选择继承危险状态的 `CMD` 或 `ENTRYPOINT`; +3. 没有显式运行入口时,选择能够证明危险权限被使用的执行指令,并在 `desc` 中明确它只代表构建阶段。 + +必须区分“直接以 root 运行”和“非 root 用户可以通过 sudo 提权”。 + +### `trace` + +`trace` 只记录配置状态的改变、激活或验证节点: + +- 普通 `COPY`、`WORKDIR`、安装和编译步骤不应为了补全链路而加入; +- 两端已经能完整说明问题时,使用 `"trace": []`; +- `entry_point` 和 `critical_operation` 可以指向同一危险配置,trace 可记录后续生效证据。 + +## 3. `desc` 编写要求 + +`desc` 应说明: + +1. 当前节点如何引入、保留或证明危险配置; +2. 该配置为什么违反最小权限等安全要求。 + +描述缺失项时应给出检查范围,例如: + +> 检查本 Dockerfile 第 1 至 23 行,未发现 USER 指令覆盖基础镜像的 root 默认用户。 + +避免使用“权限沿 COPY 传播”“调用栈”等不符合配置语义的表述,也不能推断代码无法证明的运行状态。 + +## 4. OpenClaw 样本处理 + +| Entry | `entry_point` | `critical_operation` | `trace` | +|---|---|---|---| +| `entry-00241` | `FROM` 引入 root 默认值 | `CMD` 使配置在运行时生效 | 空 | +| `entry-00242` | `FROM` 引入 root 默认值 | 最后的 `RUN pnpm install`,仅代表构建阶段 | 空 | +| `entry-00243` | `FROM` 引入 root 默认值 | `ENTRYPOINT` 使配置在运行时生效 | 空 | +| `entry-00244` | `NOPASSWD:ALL` | 同一危险配置 | `USER app` 和后续 sudo 命令 | + +四条样本均可由当前 schema 表达,不需要从 benchmark 删除。 + +## 5. 向后兼容建议 + +当前修复不增加字段。未来可考虑增加可选字段: + +```json +{ + "analysis_kind": "configuration", + "negative_evidence": { + "file": "scripts/e2e/Dockerfile", + "scope": "1-23", + "expected": "USER ", + "desc": "检查范围内未声明 USER。" + } +} +``` + +可选字段不会改变现有字段类型,旧消费者可以直接忽略。 + +修正后的记录继续保留 `verify: 0`,人工确认代码、行号和语义后才能改为 `1`。 diff --git a/config_vuln_changes.csv b/config_vuln_changes.csv new file mode 100644 index 0000000..406c8cc --- /dev/null +++ b/config_vuln_changes.csv @@ -0,0 +1,14 @@ +entry_id,field,before,after,reason +entry-00241,entry_point.desc,"把普通构建层描述为 root 特权传播","明确 FROM 引入 root 默认值,并记录 1-23 行缺少 USER","配置状态不是污点传播" +entry-00241,critical_operation.desc,"使用容器逃逸门槛等泛化影响描述","明确 CMD 是 root 默认身份的运行时生效点","解释节点为什么构成关键操作" +entry-00241,trace,"7 个节点,包含 FROM、RUN、WORKDIR 和 CMD","空数组","中间构建指令没有改变有效用户配置" +entry-00242,entry_point.desc,"声称后续所有容器应用进程均继承 root","只确认 1-9 行缺少 USER,后续显式构建命令为 root","文件没有显式 CMD 或 ENTRYPOINT" +entry-00242,critical_operation.desc,"推断依赖注入攻击,未区分构建与运行阶段","限定为 pnpm 安装及生命周期脚本在构建阶段以 root 执行","避免超出代码证据" +entry-00242,trace,"5 个节点并声称 root 延伸至容器运行时","空数组","FROM 到最终 RUN 的关系直接,且没有显式运行入口" +entry-00243,entry_point.desc,"把所有中间指令称为特权传播链","明确 FROM 默认用户以及 1-14 行缺少 USER","配置继承不需要伪造传播链" +entry-00243,critical_operation.desc,"以传播链终止节点描述 ENTRYPOINT","明确 ENTRYPOINT 是 root 配置的运行时生效点","使用配置生效语义" +entry-00243,trace,"5 个节点,包含 apt-get、COPY、chmod","空数组","这些指令没有改变运行身份" +entry-00244,entry_point,"FROM ubuntu:24.04(并错误声称最终进程继承 root)","第 18 行 NOPASSWD:ALL 配置","文件第 20 行实际存在 USER app" +entry-00244,critical_operation,"第 29 行 ENTRYPOINT","第 18 行 NOPASSWD:ALL 配置","核心缺陷是无限制免密 sudo,而非 ENTRYPOINT 直接以 root 启动" +entry-00244,trace,"7 个节点,混合基础镜像、构建步骤和运行入口","第 20 行 USER app 与第 27 行 sudo chmod 两个节点","只保留配置激活与可用性证据" +GHSA-W7J5-J98M-W679,report.vuln_title,"OpenClaw has multiple E2E/test Dockerfiles that run all processes as root - Dockerfile","OpenClaw has multiple E2E/test Dockerfiles that run all processes as root","恢复 GitHub Advisory 的规范标题" diff --git a/data/entries.jsonl b/data/entries.jsonl index 43f86b3..b1f0f28 100644 --- a/data/entries.jsonl +++ b/data/entries.jsonl @@ -159,10 +159,10 @@ {"commit": "7b4d2cb5cb8cc971e5eee880a489c227bda375fb", "critical_operation": {"code": " if (deniedFlags.has(flag)) {", "desc": "deniedFlags.has(flag) 对已解析出的长选项名称执行精确 Set 查找,判断该选项是否属于被禁标志集合。由于 Set.has 基于严格字符串相等性,不具备任何前缀展开或规范化能力,传入的缩写形式(例如仅包含部分字符的唯一前缀)无法命中集合中的完整标志名,检查静默返回 false。这是漏洞的核心判定点:精确匹配的缺失使缩写形式的被禁标志得以绕过拒绝逻辑,从而让本应被阻断的参数顺利通过校验。", "file": "src/infra/exec-safe-bin-policy.ts", "line": 304}, "entry_id": "entry-00232", "entry_point": {"code": " execute: async (_toolCallId, args, signal, onUpdate) => {", "desc": "execute 是 exec 工具的异步入口函数,负责接收来自 Agent 运行时的工具调用参数,包括待执行 shell 命令的名称与参数向量。所有通过 exec 工具发起的命令均从此处进入系统,并向下传递至安全网关层。在漏洞链路中,这是携带缩写长选项的恶意 sort 命令进入 OpenClaw 执行管道的第一个节点,也是整条缺陷传播路径的起点。", "file": "src/agents/bash-tools.exec.ts", "line": 205}, "origin": "GitHub Advisory Database (reviewed)", "project": "openclaw", "repo_url": "https://github.com/openclaw/openclaw", "report_id": "GHSA-3C6H-G97W-FG78", "source_link": "https://github.com/advisories/GHSA-3c6h-g97w-fg78", "trace": [{"code": " execute: async (_toolCallId, args, signal, onUpdate) => {", "desc": "execute 作为整条调用链的起始节点,在此步骤接收工具调用 ID、参数对象、中止信号与更新回调,是命令在进入任何安全校验之前的最初状态。该函数的签名表明它处于 Agent 运行时与内部执行管道的边界位置;在漏洞链路中,携带缩写被禁标志的 sort 命令正是从这里首次进入系统,并即将沿调用栈向下流入 allowlist 验证层。", "file": "src/agents/bash-tools.exec.ts", "line": 205}, {"code": " const gatewayResult = await processGatewayAllowlist({", "desc": "随后,execute 在完成初步参数解析后,以 await 方式调用 processGatewayAllowlist,将解析后的命令对象移交至安全网关层进行 allowlist 评估;返回值 gatewayResult 将决定命令能否在无需人工审批的情况下直接执行。这一异步调用是从工具执行层到安全网关层的跨层递交节点,深层缺陷产生的错误判定将通过 gatewayResult 沿调用栈向上传播。", "file": "src/agents/bash-tools.exec.ts", "line": 418}, {"code": " const allowlistEval = evaluateShellAllowlist({", "desc": "在调用栈下游,网关层调用 evaluateShellAllowlist 对 shell 命令执行 allowlist 策略评估,结果写入 allowlistEval。该函数作为网关逻辑与逐二进制安全检查之间的分发层,其返回值将直接影响后续 allowlistSatisfied 的布尔判定。随后由于底层选项解析器对缩写长选项缺乏规范化处理,allowlistEval 将错误地反映命令符合 allowlist 约束的结论。", "file": "src/agents/bash-tools.exec-host-gateway.ts", "line": 72}, {"code": " const safe = isSafeBinUsage({", "desc": "随后,isSafeBinUsage 以解析得到的二进制名称及参数向量为输入,判断特定可执行文件的调用方式是否处于该二进制对应安全 profile 的允许范围之内,并将结果赋给布尔变量 safe。这是逐二进制的安全门控节点;在调用栈更深层的参数校验因精确匹配缺陷对缩写标志放行后,safe 将被置为 true,为上游 allowlist 满足条件的误判提供最终的布尔依据。", "file": "src/infra/exec-approvals-allowlist.ts", "line": 134}, {"code": " return validateSafeBinArgv(argv, profile);", "desc": "在调用栈下游,validateSafeBinArgv 接受完整参数向量 argv 与二进制安全 profile,逐一遍历参数,对照 allowedValueFlags 与 deniedFlags 进行合规校验,其返回值即成为 safe 布尔量。这是到达底层 token 消费器之前的最后一次分发调用;argv 中含有缩写长选项的参数在此未经任何前缀展开处理,被原样转发至 consumeLongOptionToken,使缺陷得以延续至下一步。", "file": "src/infra/exec-approvals-allowlist.ts", "line": 92}, {"code": " const nextIndex = consumeLongOptionToken(", "desc": "随后,consumeLongOptionToken 被调用以从 argv 中提取单个长选项 token,解析出选项名称 flag 与可选内联值,并以 nextIndex 返回本次消耗的参数槽数量。该函数在处理带内联值的长选项时,将输入的缩写字符串原样作为 flag 返回,不执行与已知选项集合的前缀匹配或全名展开;由此产出的非规范 flag 值将直接进入下一步的精确匹配判定,成为绕过路径上的直接前驱。", "file": "src/infra/exec-safe-bin-policy.ts", "line": 394}, {"code": " if (deniedFlags.has(flag)) {", "desc": "在 trace 末节点,deniedFlags.has(flag) 再次执行精确 Set 查找。此时 flag 携带的是 consumeLongOptionToken 原样返回的缩写字符串,deniedFlags 中不存在该键,查找返回 false,被禁选项被静默放行。校验函数最终向上游返回通过结论,allowlistSatisfied 随即被设为 true,requiresAsk 被设为 false,本应触发人工审批的命令绕过授权直接进入执行阶段,漏洞链路在此完成闭合。", "file": "src/infra/exec-safe-bin-policy.ts", "line": 304}], "verify": 1, "vuln_category_l1": "业务逻辑", "vuln_category_l2": "BL-AGENT-CAPABILITY(AI/Agent能力边界绕过)", "vuln_ids": ["CVE-2026-32059", "GHSA-3C6H-G97W-FG78"], "vuln_title": "OpenClaw safeBins sort long-option abbreviation bypass allows exec approval bypass (RCE)"} {"commit": "861718e4dcbd33354b87b84bf5df8c9d92d21307", "critical_operation": {"code": " if !inSingle, !inDouble {", "desc": "splitShellCommandChain 字符遍历循环中,此条件仅在 inSingle 与 inDouble 同时为 false 时才触发 shouldFailClosedForUnquotedShell 的安全检测。当解析器处于双引号上下文(inDouble 为 true)时,条件不成立,整个检测分支被跳过,命令替换 token 直接追加入当前 segment,allowlist 解析器的 fail-closed 机制在此完全失效,是本漏洞的核心缺陷所在。", "file": "apps/macos/Sources/OpenClaw/ExecCommandResolution.swift", "line": 197}, "entry_id": "entry-00233", "entry_point": {"code": " var command: [String]", "desc": "ExecHostRequest 结构体中的 command 字段声明为字符串数组,用于承载经 Unix socket 传入的外部命令参数序列。该字段内容由 node-host 调用方直接构造,是携带命令替换语法的恶意内容进入 macOS companion app 处理流程的起始数据承载点,后续所有安全评估均以此字段值为输入。", "file": "apps/macos/Sources/OpenClaw/ExecApprovalsSocket.swift", "line": 42}, "origin": "GitHub Advisory Database (reviewed)", "project": "openclaw", "repo_url": "https://github.com/openclaw/openclaw", "report_id": "GHSA-9P38-94JF-HGJJ", "source_link": "https://github.com/advisories/GHSA-9p38-94jf-hgjj", "trace": [{"code": " let response = await self.onExec(payload)", "desc": "socket 消息接收层将携带外部命令内容的 payload 异步传递给 onExec 回调,将 ExecHostRequest 实例从网络 I/O 边界移交至命令审批处理流程。这是外部输入由 socket 层流向内部执行逻辑的首个调用跨越,随后由 handle 函数承接处理。", "file": "apps/macos/Sources/OpenClaw/ExecApprovalsSocket.swift", "line": 765}, {"code": " static func handle(_ request: ExecHostRequest) async -> ExecHostResponse {", "desc": "handle 作为 onExec 的直接下游,接收 ExecHostRequest 并对 command 数组各元素做空白字符裁剪后继续向下传递。随后它协调审批上下文的构建,将命令内容引导进入安全评估流程,是命令从 socket 层进入 allowlist 检查链路的中转节点。", "file": "apps/macos/Sources/OpenClaw/ExecApprovalsSocket.swift", "line": 355}, {"code": " private static func buildContext(request: ExecHostRequest, command: [String]) async -> ExecApprovalContext {", "desc": "buildContext 由 handle 调用,将 command 数组与 rawCommand 传入 ExecApprovalEvaluator.evaluate,启动针对该请求的 allowlist 安全评估。在调用栈下游,evaluate 将依据解析结果决定命令是否满足 allowlist 策略,buildContext 是命令内容从请求对象流向安全评估逻辑的衔接点。", "file": "apps/macos/Sources/OpenClaw/ExecApprovalsSocket.swift", "line": 454}, {"code": " let allowlistResolutions = ExecCommandResolution.resolveForAllowlist(", "desc": "ExecApprovalEvaluator 内部调用 ExecCommandResolution.resolveForAllowlist,将 command 与 rawCommand 传入以获取可执行文件名解析列表,供后续 allowlist 匹配使用。随后该解析结果将决定安全判定能否通过,因此解析阶段对命令替换 token 的任何遗漏都会直接影响最终放行结论。", "file": "apps/macos/Sources/OpenClaw/ExecApprovalEvaluation.swift", "line": 33}, {"code": " let shell = self.extractShellCommandFromArgv(command: command, rawCommand: rawCommand)", "desc": "resolveForAllowlist 调用 extractShellCommandFromArgv 检测命令是否为 shell 包装器形式;确认后提取 -c 参数携带的 shell 命令字符串,并将其传入 splitShellCommandChain 进行命令链分割。这是 shell 命令字符串从参数数组进入字符级逐字解析阶段的入口,随后的引号状态跟踪在此分支开始生效。", "file": "apps/macos/Sources/OpenClaw/ExecCommandResolution.swift", "line": 28}, {"code": " private static func splitShellCommandChain(_ command: String) -> [String]? {", "desc": "splitShellCommandChain 从 caller 的 shell wrapper 处理分支接收命令字符串,初始化 inSingle、inDouble、escaped 等引号跟踪状态后启动逐字符遍历。该函数的解析结果决定 allowlist 评估所依赖的可执行文件 segment 集合,其字符级状态机的行为直接关系到命令替换检测是否被触发。", "file": "apps/macos/Sources/OpenClaw/ExecCommandResolution.swift", "line": 145}, {"code": " if ch == \"\\\"\", !inSingle {", "desc": "splitShellCommandChain 遍历循环中,当前字符为双引号且不处于单引号上下文时,inDouble.toggle() 将 inDouble 翻转为 true,当前字符追加至 segment 后循环继续。随后所有字符均在双引号上下文中被处理,命令替换检测条件因 inDouble 为 true 而无法满足,此处是双引号绕过路径的直接触发点。", "file": "apps/macos/Sources/OpenClaw/ExecCommandResolution.swift", "line": 190}, {"code": " if !inSingle, !inDouble {", "desc": "在 splitShellCommandChain 遍历循环中,此分支紧接双引号状态切换逻辑之后求值:当 inDouble 为 true 时条件不成立,shouldFailClosedForUnquotedShell 不被调用,当前字符直接积入 segment,双引号内的命令替换 token 不触发 fail-closed 路径,从 splitShellCommandChain 到安全检测函数的调用链在此断裂,allowlist 解析结果含有恶意 token。", "file": "apps/macos/Sources/OpenClaw/ExecCommandResolution.swift", "line": 197}, {"code": " let allowlistMatches = security == .allowlist", "desc": "随后 ExecApprovalEvaluator 以 allowlistResolutions 中解析出的可执行文件名与 allowlist 配置条目进行匹配:由于 splitShellCommandChain 未拒绝含命令替换的命令字符串,解析结果仅含表层合法的可执行文件名,matchAll 判定为满足,命令在安全检查阶段通过,进入执行阶段。", "file": "apps/macos/Sources/OpenClaw/ExecApprovalEvaluation.swift", "line": 38}, {"code": " return await self.runCommand(", "desc": "allowlist 评估通过后,runCommand 以含命令替换语法的原始 command 数组为参数被调用,命令内容连同工作目录和环境变量一并传入底层 shell 执行器。shell 在实际执行时展开嵌入的任意命令内容,完成命令注入,漏洞利用链在此终结。", "file": "apps/macos/Sources/OpenClaw/ExecApprovalsSocket.swift", "line": 447}], "verify": 1, "vuln_category_l1": "业务逻辑", "vuln_category_l2": "BL-AGENT-CAPABILITY(AI/Agent能力边界绕过)", "vuln_ids": ["CVE-2026-22179", "GHSA-9P38-94JF-HGJJ"], "vuln_title": "OpenClaw macOS OS Command Injection via Command Substitution Bypass in Allowlist Parser (CVE-2026-22179)"} {"commit": "42d2a6188864a9c114209e35b8ea3c192792667c", "critical_operation": {"code": " const matchesSafeBin =", "desc": "计算 matchesSafeBin 的赋值表达式,通过 params.safeBins.has(execName) 判断文件名是否命中白名单。此处是漏洞的核心缺陷点:比对逻辑只基于纯文件名,完全缺失对 resolvedPath 父目录是否属于受信路径的校验。即使 resolvedPath 指向攻击者放置于任意目录下的恶意二进制,只要文件名命中白名单,matchesSafeBin 便为 true,为授权绕过提供直接条件。", "file": "src/infra/exec-approvals-allowlist.ts", "line": 84}, "entry_id": "entry-00240", "entry_point": {"code": " const envPath = env?.PATH ?? env?.Path ?? process.env.PATH ?? process.env.Path ?? \"\";", "desc": "从传入环境对象或进程环境中提取 PATH 字符串,作为后续可执行文件搜索的目录序列来源。此处是漏洞链路的起点:若调用方环境中的 PATH 被攻击者提前注入恶意目录,该字符串将原封不动地流入 resolveExecutablePath 的路径遍历逻辑,为后续目录未校验的授权判断埋下隐患。", "file": "src/infra/exec-approvals-analysis.ts", "line": 70}, "origin": "GitHub Advisory Database (reviewed)", "project": "openclaw", "repo_url": "https://github.com/openclaw/openclaw", "report_id": "GHSA-G75X-8QQM-2VXP", "source_link": "https://github.com/advisories/GHSA-g75x-8qqm-2vxp", "trace": [{"code": " for (const entry of entries) {", "desc": "位于 resolveExecutablePath 函数内部,从 caller(exec-approvals-analysis 模块的上层分析逻辑)流向此处的遍历步骤。循环依次检查 envPath 分割出的各目录 entry,对每个目录拼接候选路径并测试其可执行性;一旦发现第一个匹配的文件即返回,无论该目录是否属于受信范围。此逻辑直接将攻击者注入的 PATH 靠前目录视为合法搜索起点。", "file": "src/infra/exec-approvals-analysis.ts", "line": 87}, {"code": " const resolvedPath = resolveExecutablePath(rawExecutable, cwd, env);", "desc": "随后在分析函数中调用 resolveExecutablePath,将 rawExecutable、cwd 及 env 传入,从 callee 取回完整的 resolvedPath。取回后立即执行 path.basename(resolvedPath) 得到 executableName,目录部分在此步骤被丢弃,仅保留文件名。这一截断操作使下游 allowlist 模块永远无法感知可执行文件真实所在的目录,是漏洞链路上关键的信息丢失节点。", "file": "src/infra/exec-approvals-analysis.ts", "line": 107}, {"code": " const execName = resolution?.executableName?.toLowerCase();", "desc": "在调用栈下游的 isSafeBinUsage 函数中,从 caller 传入的 resolution 对象取出 executableName 并转为小写,赋给 execName。此时 execName 已是纯文件名字符串,resolution.resolvedPath 中携带的目录信息虽仍然存在于同一对象内,却未被引用。随后的 safeBins 比对将完全依赖这个脱离了路径上下文的文件名,为漏洞的最终触发做好铺垫。", "file": "src/infra/exec-approvals-allowlist.ts", "line": 80}, {"code": " const matchesSafeBin =", "desc": "紧接 execName 提取之后,此处承接 caller 流经 execName 赋值步骤后的控制流,正式计算 matchesSafeBin。params.safeBins.has(execName) 的 Set 查找以纯文件名为键,与 resolvedPath 的目录部分完全解耦。若文件名命中白名单,matchesSafeBin 立即置为 true,此后流程不再有任何机会对目录合法性进行二次检查。", "file": "src/infra/exec-approvals-allowlist.ts", "line": 84}, {"code": " if (!resolution?.resolvedPath) {", "desc": "在 matchesSafeBin 赋值之后,控制流进入对 resolvedPath 的存在性检查。此处仅验证 resolution.resolvedPath 是否为非空值,若路径字符串非空则直接跳过 return false 分支继续执行。检查粒度仅为有无路径,而非路径是否来自受信目录,恶意路径(如指向攻击者目录的完整绝对路径)因非空而顺利通过,授权流程继续向下推进。", "file": "src/infra/exec-approvals-allowlist.ts", "line": 90}, {"code": " return true;", "desc": "漏洞链路在此处完成闭合:isSafeBinUsage 函数经过文件名命中白名单、resolvedPath 非空两项检查后,向 caller(evaluateSegments)返回 true,声明本次命令使用属于合法 safeBin 范围。整个函数体内始终未对 resolvedPath 的父目录执行任何受信验证,因此此 return true 实际上等同于对恶意路径颁发了与合法系统二进制相同的授权。", "file": "src/infra/exec-approvals-allowlist.ts", "line": 127}, {"code": " const allowlistEval = evaluateShellAllowlist({", "desc": "调用栈的上游入口,位于 bash-tools.exec 模块的命令执行处理逻辑中,作为 caller 将 command、safeBins、cwd 及 env 打包传入 evaluateShellAllowlist,触发从此处向下游 evaluateSegments 再到 isSafeBinUsage 的整条 allowlist 评估链路。env 参数中携带的 PATH 值此刻尚未经过任何目录可信度过滤,最终将驱动有缺陷的 safeBin 校验流程得出 allowlistSatisfied = true 的结论。", "file": "src/agents/bash-tools.exec.ts", "line": 707}, {"code": " if (hostSecurity === \"allowlist\" && (!analysisOk || !allowlistSatisfied)) {", "desc": "链路的终点,bash-tools.exec 的安全门控逻辑。在 evaluateShellAllowlist 返回 allowlistSatisfied = true 之后,此处的条件判断要求 allowlistSatisfied 为 false 才能触发拒绝分支,而经由有缺陷的 safeBin 路径校验所产生的 true 值使条件恒不成立,exec 命令因此顺利绕过最终拦截,进入实际执行阶段。至此,整条从 PATH 注入到命令执行的授权绕过链路全部贯通。", "file": "src/agents/bash-tools.exec.ts", "line": 907}], "verify": 1, "vuln_category_l1": "业务逻辑", "vuln_category_l2": "BL-AGENT-CAPABILITY(AI/Agent能力边界绕过)", "vuln_ids": ["CVE-2026-32015", "GHSA-G75X-8QQM-2VXP"], "vuln_title": "openclaw safeBins allowlist validation bypass via PATH manipulation"} -{"commit": "c56fb7f353d63d6ea97028ee7d8a97bc4edf21c1", "critical_operation": {"code": "CMD [\"bash\"]", "desc": "CMD 指令将 bash 设为容器的默认启动进程,由于整个 Dockerfile 从未插入 USER 指令切换至非特权账户,bash 进程在容器启动时以 root(uid=0)身份运行。这是过度特权漏洞的最终兑现点:任何获得 bash 会话的操作均拥有完整 root 权限,显著降低容器逃逸门槛。", "file": "scripts/e2e/Dockerfile", "line": 23}, "entry_id": "entry-00241", "entry_point": {"code": "FROM node:22-bookworm", "desc": "FROM 指令以 node:22-bookworm 作为基础镜像,该镜像内置默认用户为 root(uid=0)。整个构建文件自此层起始,既无 RUN useradd 创建非特权账户,也无 USER 指令切换身份,导致后续所有构建层次与最终容器进程均继承 root 特权上下文,构成 CWE-250 过度特权漏洞的源头。", "file": "scripts/e2e/Dockerfile", "line": 1}, "origin": "GitHub Advisory Database (reviewed)", "project": "openclaw", "repo_url": "https://github.com/openclaw/openclaw", "report_id": "GHSA-W7J5-J98M-W679", "source_link": "https://github.com/advisories/GHSA-w7j5-j98m-w679", "trace": [{"code": "FROM node:22-bookworm", "desc": "构建阶段起始于 FROM node:22-bookworm,该基础镜像将 root 设为默认活跃用户。此构建层次奠定了整条特权传递路径的基础:若后续层次中不显式调用 USER 指令,root 特权状态将向下贯穿所有 RUN、WORKDIR 及最终 CMD 层,是漏洞链路的第一个节点。", "file": "scripts/e2e/Dockerfile", "line": 1}, {"code": "RUN corepack enable", "desc": "RUN corepack enable 以 root 身份在构建层次中激活 Node.js 包管理器框架 corepack。由于此时尚无 USER 指令将身份切换为非特权用户,该命令及其对系统目录的写操作全程以 uid=0 执行;随后的依赖安装与构建步骤将在同一 root 上下文中继续推进,特权状态沿构建阶段向下传递。", "file": "scripts/e2e/Dockerfile", "line": 3}, {"code": "WORKDIR /app", "desc": "WORKDIR /app 在 root 身份下创建或切换到 /app 工作目录,该目录及其所有后续写入内容均归属 root 所有。随后的依赖安装与编译操作将在此目录中以 root 权限进行,构建层次中的文件系统权限设置进一步固化了 root 特权上下文,为漏洞链路的延续提供了环境基础。", "file": "scripts/e2e/Dockerfile", "line": 5}, {"code": "RUN pnpm install --frozen-lockfile", "desc": "RUN pnpm install --frozen-lockfile 在 root 权限下执行依赖包的锁定安装,所有 node_modules 文件均以 root 身份写入文件系统。在构建阶段向下传递的过程中,此步骤延续了自 FROM 层以来的 root 特权上下文,且仍无任何 USER 指令介入,依赖安装完成后特权状态随构建缓存层持续存在。", "file": "scripts/e2e/Dockerfile", "line": 19}, {"code": "RUN pnpm build", "desc": "RUN pnpm build 以 root 身份执行项目主体的编译构建流程,生成的构建产物及中间文件均归 root 所有。随后此特权状态在构建层次中持续向下传递;构建过程中若存在任何供应链风险,root 权限将使其影响范围扩展至整个容器文件系统,加重了漏洞的潜在危害。", "file": "scripts/e2e/Dockerfile", "line": 20}, {"code": "RUN pnpm ui:build", "desc": "RUN pnpm ui:build 在 root 上下文中完成前端界面资源的编译,是构建阶段的最后一个 RUN 层次。至此,所有构建操作均以 root 身份完成且未引入任何用户切换,随后镜像即进入 CMD 声明阶段;特权状态在构建链路末端仍未被约束,直接延续至容器运行时。", "file": "scripts/e2e/Dockerfile", "line": 21}, {"code": "CMD [\"bash\"]", "desc": "CMD [\"bash\"] 是构建层次链路的终点,将 bash shell 注册为容器默认启动进程。由于从 FROM 指令至此的全部构建阶段均未出现 USER 切换,bash 进程在容器启动时继承了完整的 root 特权,与 FROM 层确立的 root 上下文形成闭合;任何进入容器的操作入口均具备 uid=0 的完整权限。", "file": "scripts/e2e/Dockerfile", "line": 23}], "verify": 0, "vuln_category_l1": "特权提升", "vuln_category_l2": "容器以不必要特权运行", "vuln_ids": ["GHSA-W7J5-J98M-W679"], "vuln_title": "OpenClaw has multiple E2E/test Dockerfiles that run all processes as root - Dockerfile"} -{"commit": "c56fb7f353d63d6ea97028ee7d8a97bc4edf21c1", "critical_operation": {"code": "RUN pnpm install --frozen-lockfile", "desc": "此指令在 root 权限下执行 pnpm 包管理器的依赖安装,是漏洞危害落地的关键节点。由于 Dockerfile 全程缺少用户切换,安装过程及其触发的任何 install 脚本均以 uid=0 身份运行,攻击者一旦在此阶段注入恶意依赖,便可借助完整的 root 权限对容器环境实施任意操作,显著拓宽攻击面。", "file": "scripts/e2e/Dockerfile.qr-import", "line": 9}, "entry_id": "entry-00242", "entry_point": {"code": "FROM node:22-bookworm", "desc": "此行声明基础镜像为 node:22-bookworm,该镜像的默认用户为 root(uid=0)。整个漏洞的起点在此:后续所有 RUN 指令及容器内的应用进程均继承此权限上下文,而 Dockerfile 全程未添加任何用户切换指令以打破这一默认状态,形成以不必要特权运行的根源。", "file": "scripts/e2e/Dockerfile.qr-import", "line": 1}, "origin": "GitHub Advisory Database (reviewed)", "project": "openclaw", "repo_url": "https://github.com/openclaw/openclaw", "report_id": "GHSA-W7J5-J98M-W679", "source_link": "https://github.com/advisories/GHSA-w7j5-j98m-w679", "trace": [{"code": "FROM node:22-bookworm", "desc": "漏洞传播链路的起点:选用 node:22-bookworm 作为基础镜像,其内建默认用户为 root。这一选择本身无误,但问题在于后续步骤始终未通过 USER 指令覆盖此默认值,使 root 身份从此处开始贯穿镜像构建的每一个阶段。", "file": "scripts/e2e/Dockerfile.qr-import", "line": 1}, {"code": "RUN corepack enable", "desc": "接着在第二步,以继承自基础镜像的 root 身份启用 corepack,令 Node.js 原生包管理器切换机制在特权上下文中激活。这一操作本为常规配置,但在整个 Dockerfile 全程无 USER 指令的背景下,意味着包管理工具链的初始化同样由 root 完成,持续传递特权执行环境至后续步骤。", "file": "scripts/e2e/Dockerfile.qr-import", "line": 3}, {"code": "WORKDIR /app", "desc": "随后将工作目录切换至 /app,此操作由 root 执行,目录所有权归 root 所有。在漏洞链路中,这一步为后续文件复制与依赖安装预设了 root 属主的文件系统上下文,使得整个应用的工作区从一开始便处于高特权环境中,进一步加深了权限滥用的潜在影响范围。", "file": "scripts/e2e/Dockerfile.qr-import", "line": 5}, {"code": "COPY . .", "desc": "紧接着将宿主机项目目录的全部内容复制至容器工作区,复制操作在 root 上下文中执行,写入的所有文件均以 root 为属主。在漏洞链路中,这一步使应用代码以完全特权的状态落入容器,为随后在 root 身份下执行依赖安装脚本提供了条件,同时也意味着任何敏感配置文件将暴露于不受限制的特权进程之下。", "file": "scripts/e2e/Dockerfile.qr-import", "line": 7}, {"code": "RUN pnpm install --frozen-lockfile", "desc": "最终,在链路末端以 root 身份执行依赖安装,完成对整条特权传播路径的收束。从基础镜像引入 root 默认用户开始,经过 corepack 激活、工作目录创建、代码文件写入,直至此处的包安装与 install 钩子执行,整条链路始终未发生用户身份降级,root 特权贯穿构建全程并延伸至容器运行时,构成不必要特权执行的完整闭环。", "file": "scripts/e2e/Dockerfile.qr-import", "line": 9}], "verify": 0, "vuln_category_l1": "特权提升", "vuln_category_l2": "容器以不必要特权运行", "vuln_ids": ["GHSA-W7J5-J98M-W679"], "vuln_title": "OpenClaw has multiple E2E/test Dockerfiles that run all processes as root - Dockerfile.qr-import"} -{"commit": "c56fb7f353d63d6ea97028ee7d8a97bc4edf21c1", "critical_operation": {"code": "ENTRYPOINT [\"/usr/local/bin/openclaw-install-e2e\"]", "desc": "ENTRYPOINT 指令将 /usr/local/bin/openclaw-install-e2e 注册为容器默认启动入口。由于 FROM 层确立的 root 身份自始至终未被 USER 指令中断,该脚本在容器实例化时以 uid 0 被调用,是特权传播链的终止节点,也是 CWE-250 过度特权在运行时得以完整兑现的关键指令。", "file": "scripts/docker/install-sh-e2e/Dockerfile", "line": 14}, "entry_id": "entry-00243", "entry_point": {"code": "FROM node:22-bookworm-slim", "desc": "FROM node:22-bookworm-slim 是镜像构建的基础声明,引入该基础镜像后默认用户即为 root(uid 0)。该 Dockerfile 全文未出现任何 USER 指令来覆盖这一默认身份,导致构建阶段与运行时所有操作均在 root 上下文中进行,是 CWE-250 特权传播链的起始锚点。", "file": "scripts/docker/install-sh-e2e/Dockerfile", "line": 1}, "origin": "GitHub Advisory Database (reviewed)", "project": "openclaw", "repo_url": "https://github.com/openclaw/openclaw", "report_id": "GHSA-W7J5-J98M-W679", "source_link": "https://github.com/advisories/GHSA-w7j5-j98m-w679", "trace": [{"code": "FROM node:22-bookworm-slim", "desc": "传播链首步:FROM node:22-bookworm-slim 确立镜像构建基点,该基础镜像以 root(uid 0)作为内置默认用户。此处未附加 USER 声明,使后续所有 RUN、COPY、ENTRYPOINT 指令均在未受限制的 root 上下文中执行,为整条特权传播链提供根基。", "file": "scripts/docker/install-sh-e2e/Dockerfile", "line": 1}, {"code": "RUN apt-get update \\", "desc": "随后,RUN apt-get update 以 FROM 层继承的 root 身份执行软件包索引刷新与运行时依赖安装(bash、ca-certificates、curl、git 等)。以 root 身份进行系统包管理意味着所安装内容对系统路径拥有无限制写权限,在调用链上游确立了高特权的构建环境,为后续工件部署步骤创造前提。", "file": "scripts/docker/install-sh-e2e/Dockerfile", "line": 3}, {"code": "COPY run.sh /usr/local/bin/openclaw-install-e2e", "desc": "在调用链下游,COPY 指令以 root 身份将构建上下文中的 run.sh 植入系统级路径 /usr/local/bin/openclaw-install-e2e。系统二进制目录普通用户无法写入;以 root 执行复制使该文件继承高权限属主,是构建时 root 特权向运行时可执行工件传递的关键衔接节点。", "file": "scripts/docker/install-sh-e2e/Dockerfile", "line": 11}, {"code": "RUN chmod +x /usr/local/bin/openclaw-install-e2e", "desc": "随后,RUN chmod +x 在持续的 root 上下文中为 /usr/local/bin/openclaw-install-e2e 设置可执行位,完成对前一步骤所部署脚本的权限激活。该操作是后续 ENTRYPOINT 指令能够在容器启动时直接调用该脚本的必要前提,充当特权传播链的倒数第二环。", "file": "scripts/docker/install-sh-e2e/Dockerfile", "line": 12}, {"code": "ENTRYPOINT [\"/usr/local/bin/openclaw-install-e2e\"]", "desc": "传播链末步:ENTRYPOINT 将经 chmod +x 激活的 /usr/local/bin/openclaw-install-e2e 设定为容器主进程。全程未出现 USER 指令,该脚本在容器启动时以 uid 0 运行,特权传播链在此完全闭合,CWE-250 过度特权在运行时最终得到完整实现。", "file": "scripts/docker/install-sh-e2e/Dockerfile", "line": 14}], "verify": 0, "vuln_category_l1": "特权提升", "vuln_category_l2": "容器以不必要特权运行", "vuln_ids": ["GHSA-W7J5-J98M-W679"], "vuln_title": "OpenClaw has multiple E2E/test Dockerfiles that run all processes as root - Dockerfile"} -{"commit": "c56fb7f353d63d6ea97028ee7d8a97bc4edf21c1", "critical_operation": {"code": "ENTRYPOINT [\"/usr/local/bin/openclaw-install-nonroot\"]", "desc": "ENTRYPOINT 指令将 openclaw-install-nonroot 脚本设为容器启动时的入口进程,尽管其路径包含 nonroot 字样。该脚本在具备免密全局 sudo 授权的 app 用户身份下启动,可无需任何凭证切换为完整 root 权限;此前由 USER app 带来的降权效果在此完全失效。这一步是漏洞链路的最终汇聚点:从基础镜像到 sudo 授权再到形式降权,所有环节的缺陷最终由此指令暴露为容器进程持有实质 root 能力的运行态。", "file": "scripts/docker/install-sh-nonroot/Dockerfile", "line": 29}, "entry_id": "entry-00244", "entry_point": {"code": "FROM ubuntu:24.04", "desc": "FROM ubuntu:24.04 将基础镜像确立为 ubuntu:24.04,该镜像内置的默认进程执行身份为 uid 0(root)。此指令是整个权限链路的起始节点:在没有额外 USER 指令约束的情况下,镜像后续所有 RUN 层以及最终 ENTRYPOINT 进程均继承这一 root 身份运行,是漏洞链路中超额特权得以延续至容器运行时的根本前提。", "file": "scripts/docker/install-sh-nonroot/Dockerfile", "line": 1}, "origin": "GitHub Advisory Database (reviewed)", "project": "openclaw", "repo_url": "https://github.com/openclaw/openclaw", "report_id": "GHSA-W7J5-J98M-W679", "source_link": "https://github.com/advisories/GHSA-w7j5-j98m-w679", "trace": [{"code": "FROM ubuntu:24.04", "desc": "FROM ubuntu:24.04 以 ubuntu:24.04 作为构建基础层,该镜像的初始用户环境不包含任何受限账号,进程默认以 uid 0(root)身份运行。作为 trace 链路的第一步,此指令奠定了整条特权传播链的权限底色:所有后续 RUN 命令、用户配置操作以及最终的 ENTRYPOINT 均在这一 root 基础上依次构建,是漏洞链路得以形成的原始起点。", "file": "scripts/docker/install-sh-nonroot/Dockerfile", "line": 1}, {"code": "RUN set -eux; \\", "desc": "RUN set -eux 开启一段以严格错误退出模式运行的 shell 命令块,执行时构建层的有效身份仍为 root。随后该 RUN 层依次完成依赖安装与系统配置,全程持有完整 root 权限。在特权传播链路中,此步位于初始化执行阶段,为后续 app 用户的创建和 sudo 授权搭建了具有 root 能力的构建环境,是漏洞链路在构建期间的第一个可执行节点。", "file": "scripts/docker/install-sh-nonroot/Dockerfile", "line": 3}, {"code": "RUN useradd -m -s /bin/bash app \\", "desc": "useradd 命令以 root 身份创建名为 app 的系统用户,分配家目录并将 bash 设为默认 shell。在调用栈下游,此步骤完成了降权前的账号准备,表面上开启了从 root 向非特权用户的过渡。然而漏洞链路在此处出现关键转折:紧随其后的 sudo 授权操作将架空这一降权设计,使 app 的创建仅构成形式上的权限边界,在链路中扮演虚假安全边界的角色。", "file": "scripts/docker/install-sh-nonroot/Dockerfile", "line": 17}, {"code": " && echo \"app ALL=(ALL) NOPASSWD:ALL\" > /etc/sudoers.d/app", "desc": "sudoers.d/app 文件的写入操作将 app 用户配置为可以无密码方式执行任意用户的任意命令,包括 root。进而,这一配置从根本上消解了 useradd 所建立的权限分隔:app 账号在技术层面已与 root 等价,持有零成本的全权提升通道。在漏洞链路中,此步是核心特权保留操作,直接导致后续 USER app 切换在安全语义上无效,并为 ENTRYPOINT 进程保留了实质 root 能力。", "file": "scripts/docker/install-sh-nonroot/Dockerfile", "line": 18}, {"code": "USER app", "desc": "USER app 指令将后续构建层及容器运行时的进程身份切换为 app 用户,在外观上完成了权限降级。然而此时 /etc/sudoers.d/app 已授予 app 全量免密 sudo 能力,这一身份切换仅是形式上的隔离,进程在任意时刻均可无障碍提升为完整 root。在漏洞链路中,此步是可见的降权节点却不构成真正的权限边界,是 Dockerfile 路径名所暗示的 nonroot 语义与实际运行特权之间矛盾的集中体现。", "file": "scripts/docker/install-sh-nonroot/Dockerfile", "line": 20}, {"code": "RUN sudo chmod +x /usr/local/bin/openclaw-install-nonroot", "desc": "sudo chmod +x 在 app 用户身份下借助免密 sudo 以 root 权限将 openclaw-install-nonroot 修改为可执行文件。在调用栈下游,此命令直接展示了特权传播链路的完整可用性:app 通过 sudo 无缝完成文件系统级操作,印证了 NOPASSWD:ALL 授权的实际效力。这一步在漏洞链路中扮演特权验证节点的角色,同时为最终 ENTRYPOINT 以受污染的执行权限启动安装脚本完成了最后准备。", "file": "scripts/docker/install-sh-nonroot/Dockerfile", "line": 27}, {"code": "ENTRYPOINT [\"/usr/local/bin/openclaw-install-nonroot\"]", "desc": "ENTRYPOINT 将 openclaw-install-nonroot 脚本注册为容器的默认启动进程,与 critical_operation 所指示的位置相同。作为 trace 链路的终点,此指令汇聚了从 ubuntu:24.04 基础镜像出发、经 root 环境初始化、app 用户创建、sudo 全权授权、USER 形式降权、chmod 权限就绪的完整传播路径,是整条漏洞链路的最终输出节点,使容器以持有全量提权能力的身份启动安装脚本。", "file": "scripts/docker/install-sh-nonroot/Dockerfile", "line": 29}], "verify": 0, "vuln_category_l1": "特权提升", "vuln_category_l2": "容器以不必要特权运行", "vuln_ids": ["GHSA-W7J5-J98M-W679"], "vuln_title": "OpenClaw has multiple E2E/test Dockerfiles that run all processes as root - Dockerfile"} +{"commit": "c56fb7f353d63d6ea97028ee7d8a97bc4edf21c1", "critical_operation": {"code": "CMD [\"bash\"]", "desc": "CMD 将 bash 设为容器默认启动进程。由于此前没有 USER 指令覆盖基础镜像的 root 默认用户,容器启动时 bash 以 uid=0 运行;进程一旦被控制,攻击者立即取得容器内 root 权限,因此该指令是危险配置在运行时的生效点。", "file": "scripts/e2e/Dockerfile", "line": 23}, "entry_id": "entry-00241", "entry_point": {"code": "FROM node:22-bookworm", "desc": "FROM 声明引入默认用户为 root(uid=0)的 node:22-bookworm 基础镜像,是该 Dockerfile 运行身份配置的起点。检查本文件第 1 至 23 行,未发现 USER 指令覆盖这一默认身份,因此最终启动命令仍使用 root;漏洞来自不安全默认值未被显式降权,而不是普通构建指令之间的污点传播。", "file": "scripts/e2e/Dockerfile", "line": 1}, "origin": "GitHub Advisory Database (reviewed)", "project": "openclaw", "repo_url": "https://github.com/openclaw/openclaw", "report_id": "GHSA-W7J5-J98M-W679", "source_link": "https://github.com/advisories/GHSA-w7j5-j98m-w679", "trace": [], "verify": 0, "vuln_category_l1": "特权提升", "vuln_category_l2": "容器以不必要特权运行", "vuln_ids": ["GHSA-W7J5-J98M-W679"], "vuln_title": "OpenClaw has multiple E2E/test Dockerfiles that run all processes as root - Dockerfile"} +{"commit": "c56fb7f353d63d6ea97028ee7d8a97bc4edf21c1", "critical_operation": {"code": "RUN pnpm install --frozen-lockfile", "desc": "这是文件中最后一个显式执行指令,pnpm 依赖安装及其可能触发的生命周期脚本均以 root 身份运行,从而把第三方构建代码置于不必要的最高权限下。本文件没有 CMD 或 ENTRYPOINT,因此此节点只证明构建阶段的特权执行;不能将它描述为显式的容器运行入口。", "file": "scripts/e2e/Dockerfile.qr-import", "line": 9}, "entry_id": "entry-00242", "entry_point": {"code": "FROM node:22-bookworm", "desc": "FROM 声明引入默认用户为 root(uid=0)的 node:22-bookworm 基础镜像,是本文件权限配置的起点。检查第 1 至 9 行,未发现 USER 指令覆盖该默认身份;该缺失使后续显式构建命令保持 root 权限。", "file": "scripts/e2e/Dockerfile.qr-import", "line": 1}, "origin": "GitHub Advisory Database (reviewed)", "project": "openclaw", "repo_url": "https://github.com/openclaw/openclaw", "report_id": "GHSA-W7J5-J98M-W679", "source_link": "https://github.com/advisories/GHSA-w7j5-j98m-w679", "trace": [], "verify": 0, "vuln_category_l1": "特权提升", "vuln_category_l2": "容器以不必要特权运行", "vuln_ids": ["GHSA-W7J5-J98M-W679"], "vuln_title": "OpenClaw has multiple E2E/test Dockerfiles that run all processes as root - Dockerfile.qr-import"} +{"commit": "c56fb7f353d63d6ea97028ee7d8a97bc4edf21c1", "critical_operation": {"code": "ENTRYPOINT [\"/usr/local/bin/openclaw-install-e2e\"]", "desc": "ENTRYPOINT 将安装脚本设为容器主进程。由于此前没有 USER 指令覆盖基础镜像的 root 默认用户,该脚本在容器启动时以 uid=0 运行;脚本或其调用程序一旦被控制,影响可覆盖整个容器,因此这里是危险配置的运行时生效点。", "file": "scripts/docker/install-sh-e2e/Dockerfile", "line": 14}, "entry_id": "entry-00243", "entry_point": {"code": "FROM node:22-bookworm-slim", "desc": "FROM 声明引入默认用户为 root(uid=0)的 node:22-bookworm-slim 基础镜像,是该 Dockerfile 运行身份配置的起点。检查本文件第 1 至 14 行,未发现 USER 指令覆盖这一默认身份,因此容器入口继承 root;中间的包安装、复制和 chmod 不是独立的权限传播节点。", "file": "scripts/docker/install-sh-e2e/Dockerfile", "line": 1}, "origin": "GitHub Advisory Database (reviewed)", "project": "openclaw", "repo_url": "https://github.com/openclaw/openclaw", "report_id": "GHSA-W7J5-J98M-W679", "source_link": "https://github.com/advisories/GHSA-w7j5-j98m-w679", "trace": [], "verify": 0, "vuln_category_l1": "特权提升", "vuln_category_l2": "容器以不必要特权运行", "vuln_ids": ["GHSA-W7J5-J98M-W679"], "vuln_title": "OpenClaw has multiple E2E/test Dockerfiles that run all processes as root - Dockerfile"} +{"commit": "c56fb7f353d63d6ea97028ee7d8a97bc4edf21c1", "critical_operation": {"code": " && echo \"app ALL=(ALL) NOPASSWD:ALL\" > /etc/sudoers.d/app", "desc": "写入 NOPASSWD:ALL 是核心缺陷:它允许 app 无密码地以 root 或其他任意用户执行任意命令,使第 20 行 USER app 的降权只改变默认 uid,而不能形成有效的最小权限边界。危险来自无限制 sudo 授权,不是第 29 行 ENTRYPOINT 自身以 root 启动。", "file": "scripts/docker/install-sh-nonroot/Dockerfile", "line": 18}, "entry_id": "entry-00244", "entry_point": {"code": " && echo \"app ALL=(ALL) NOPASSWD:ALL\" > /etc/sudoers.d/app", "desc": "该配置声明把 app 暴露为可使用 sudo 的运行身份入口:app 无需凭证即可切换为任意用户并执行任意命令。它不是外部污点输入,而是直接决定后续权限边界的静态配置,因此配置声明本身也是漏洞分析的入口。", "file": "scripts/docker/install-sh-nonroot/Dockerfile", "line": 18}, "origin": "GitHub Advisory Database (reviewed)", "project": "openclaw", "repo_url": "https://github.com/openclaw/openclaw", "report_id": "GHSA-W7J5-J98M-W679", "source_link": "https://github.com/advisories/GHSA-w7j5-j98m-w679", "trace": [{"code": "USER app", "desc": "USER app 确实使后续构建步骤和容器默认进程以 app 身份运行,但 app 已在第 18 行获得 NOPASSWD:ALL,因此该切换不能阻止进程按需提升到 root;这是危险配置进入运行身份上下文的生效节点。", "file": "scripts/docker/install-sh-nonroot/Dockerfile", "line": 20}, {"code": "RUN sudo chmod +x /usr/local/bin/openclaw-install-nonroot", "desc": "该命令在 USER app 之后成功通过 sudo 执行需要高权限的文件操作,直接证明第 18 行免密 sudo 配置可用。它是配置生效证据,不表示最终 ENTRYPOINT 会自动以 root 运行。", "file": "scripts/docker/install-sh-nonroot/Dockerfile", "line": 27}], "verify": 0, "vuln_category_l1": "特权提升", "vuln_category_l2": "容器以不必要特权运行", "vuln_ids": ["GHSA-W7J5-J98M-W679"], "vuln_title": "OpenClaw has multiple E2E/test Dockerfiles that run all processes as root - Dockerfile"} {"commit": "204d9fb404838221df19cc46b30e4cf53209d038", "critical_operation": {"code": " if (", "desc": "evaluateSegments 内部的条件分支负责判断当前命令段是否由 skills 白名单满足:此处对 executableName 调用 skillBins.has() 做集合查找,缺少对完整解析路径是否属于受信任安装目录的校验。恰恰是这个缺失的路径作用域约束,使任意具备相同 basename 的可执行文件均可将 skillAllow 置为 true,进而令整条 allowlist 评估返回满足状态,绕过 ask=on-miss 审批流程。", "file": "src/infra/exec-approvals-allowlist.ts", "line": 159}, "entry_id": "entry-00245", "entry_point": {"code": "export async function handleSystemRunInvoke(opts: HandleSystemRunInvokeOptions): Promise {", "desc": "handleSystemRunInvoke 是系统命令执行请求的顶层入口函数,负责编排从参数准备、allowlist 策略评估到最终命令执行的完整流程。当 autoAllowSkills=true 配置生效时,此函数内部将构建 bins 集合并向下游白名单评估路径传递,为后续 basename 碰撞绕过提供初始激活条件。", "file": "src/node-host/invoke-system-run.ts", "line": 262}, "origin": "GitHub Advisory Database (reviewed)", "project": "openclaw", "repo_url": "https://github.com/openclaw/openclaw", "report_id": "GHSA-7FF8-XJH3-MGH6", "source_link": "https://github.com/advisories/GHSA-7ff8-xjh3-mgh6", "trace": [{"code": " const bins = autoAllowSkills ? await opts.skillBins.current() : new Set();", "desc": "当 autoAllowSkills 标志为 true 时,此赋值语句通过 opts.skillBins.current() 异步拉取当前已注册 skill 可执行文件的 basename 集合,并将其传递给下游 allowlist 评估路径;若为 false 则传入空集合从而完全绕开 skills 白名单分支。这是漏洞链路中首个影响后续 basename 比对的条件节点,决定了 skillBins 是否在下游被激活。", "file": "src/node-host/invoke-system-run.ts", "line": 313}, {"code": "function evaluateSystemRunAllowlist(params: {", "desc": "evaluateSystemRunAllowlist 是从 handleSystemRunInvoke 调用栈向下传递的中间包装函数,将 bins(skill 可执行文件 basename 集合)、命令参数及安全策略参数聚合为结构化入参,随后转发给 evaluateSegments 做逐段评估。此函数在调用栈中充当入口层与核心评估层之间的参数聚合桥梁,其传入的 skillBins 直接影响下游 basename 匹配结果。", "file": "src/node-host/invoke-system-run.ts", "line": 138}, {"code": "function evaluateSegments(", "desc": "evaluateSegments 接收完整的命令分段列表,对每个段依次提取 rawExecutable、计算 executableName,再逐项与 listAllowed、skillBins 等规则进行比对,最终汇总返回 allowlistSatisfied 布尔值。在调用栈下游,此函数是漏洞的直接承载体——仅依赖 basename 字符串匹配而非完整路径验证的逻辑缺陷正位于此处,随后将影响上游的审批决策。", "file": "src/infra/exec-approvals-allowlist.ts", "line": 108}, {"code": " const rawExecutable = segment.resolution?.rawExecutable?.trim() ?? \"\";", "desc": "rawExecutable 从 segment.resolution 中取出命令段对应的原始可执行文件字符串(如 ./skill-bin 或 /tmp/skill-bin),经 trim() 处理后作为后续 basename 提取的输入。随后在调用栈下游,resolveCommandResolutionFromArgv 将对此值执行 path.basename() 运算,丢弃路径前缀,使相对路径与任意绝对路径均能折叠为与合法 skill 相同的短名称,为碰撞比对埋下条件。", "file": "src/infra/exec-approvals-allowlist.ts", "line": 155}, {"code": "export function resolveCommandResolutionFromArgv(", "desc": "resolveCommandResolutionFromArgv 负责将 argv 中的可执行文件参数解析为命令解析结构体,其中包含对原始路径执行 path.basename() 所得的 executableName 字段。正是在这一步,./skill-bin 与受信任的 skill-bin 被折叠为相同的短名称,为上层 evaluateSegments 的 skillBins.has() 检查埋下路径碰撞条件,构成本次路径作用域绕过的技术基础。", "file": "src/infra/exec-command-resolution.ts", "line": 110}, {"code": " skillAllow = Boolean(params.skillBins?.has(executableName));", "desc": "skillAllow 赋值语句是白名单评估中的核心判断:仅以 executableName(basename 层面的短名称)为键查询 skillBins 集合,而不核验该可执行文件的完整解析路径是否指向受信任目录。当 ./skill-bin 经 basename 折叠后恰好命中集合中的合法条目时,skillAllow 被错误置为 true,漏洞链路的关键条件在此处满足。", "file": "src/infra/exec-approvals-allowlist.ts", "line": 166}, {"code": " const by: ExecSegmentSatisfiedBy = match", "desc": "by 变量通过模式匹配根据 skillAllow、listAllowed 等标志确定当前命令段的满足原因标记。当上一步 skillAllow 被错误置为 true 后,by 随之被设为 skills,该标记汇入 allowlistSatisfied 的计算,使整段命令被判定为受信任;在调用栈上游,这将阻止 requiresAsk 触发操作员确认流程。", "file": "src/infra/exec-approvals-allowlist.ts", "line": 168}, {"code": " const requiresAsk = requiresExecApproval({", "desc": "requiresExecApproval 根据上层返回的 allowlist 评估结果计算 requiresAsk 布尔值,决定当前命令是否需要操作员确认。当上游 evaluateSegments 因 basename 碰撞错误地将 allowlistSatisfied 置为 true 后,requiresAsk 被计算为 false,使 ask=on-miss 策略下本应触发的审批提示被静默跳过,为最终无提示执行命令铺平道路。", "file": "src/node-host/exec-policy.ts", "line": 85}, {"code": " const result = await opts.runCommand(", "desc": "opts.runCommand 是整条调用链的终点,在此处实际执行系统命令。由于上游 requiresAsk 已被 basename 碰撞绕过降为 false,命令在未经操作员审批的情况下直接运行,完成本次路径匹配绕过漏洞链路的最终环节,实现无提示的本地命令执行。", "file": "src/node-host/invoke-system-run.ts", "line": 465}], "verify": 1, "vuln_category_l1": "业务逻辑", "vuln_category_l2": "BL-AGENT-CAPABILITY(AI/Agent能力边界绕过)", "vuln_ids": ["GHSA-7FF8-XJH3-MGH6"], "vuln_title": "openclaw Incorrect Authorization via basename allowlist matching (GHSA-7ff8-xjh3-mgh6) - exec-approvals-allowlist.ts"} {"commit": "204d9fb404838221df19cc46b30e4cf53209d038", "critical_operation": {"code": "if (", "desc": "allowlist 评估器中对 skills 白名单进行判断的条件分支起点,负责决定是否启用 skill 自动放行逻辑。该条件成立后将执行下游的 basename 集合比对,但整个分支缺少对已解析可执行路径所在目录是否可信的验证;正是这一校验缺失,使攻击者可以凭借与合法 skill 同名的任意目录下的可执行文件,绕过操作员确认流程。", "file": "src/infra/exec-approvals-allowlist.ts", "line": 159}, "entry_id": "entry-00246", "entry_point": {"code": "export async function handleSystemRunInvoke(opts: HandleSystemRunInvokeOptions): Promise {", "desc": "handleSystemRunInvoke 是处理 system.run 命令调用的顶层导出函数,负责接收并分发包含命令名称、参数及环境变量覆盖在内的全部外部可控输入。在漏洞利用链中,此处是攻击者注入恶意 env.PATH 的入口起点;opts 中携带的环境变量将沿调用栈向下传递,最终影响可执行文件的路径解析结果。", "file": "src/node-host/invoke-system-run.ts", "line": 262}, "origin": "GitHub Advisory Database (reviewed)", "project": "openclaw", "repo_url": "https://github.com/openclaw/openclaw", "report_id": "GHSA-7FF8-XJH3-MGH6", "source_link": "https://github.com/advisories/GHSA-7ff8-xjh3-mgh6", "trace": [{"code": "const envOverrides = sanitizeSystemRunEnvOverrides({", "desc": "此步骤位于 handleSystemRunInvoke 内部,是入口函数将外部传入的 env 覆盖项向下游传递的第一个中间环节。调用 sanitizeSystemRunEnvOverrides 对请求方提供的 env 字段做清洗,但该清洗不足以过滤攻击者精心构造的恶意 PATH 值;随后经过 sanitizeEnv 进一步处理后,带有恶意路径前缀的 env 对象将流入命令参数解析阶段,为后续的路径劫持埋下条件。", "file": "src/node-host/invoke-system-run.ts", "line": 304}, {"code": "function resolveExecutablePath(rawExecutable: string, cwd?: string, env?: NodeJS.ProcessEnv) {", "desc": "resolveExecutablePath 是从 handleSystemRunInvoke 传递过来的 env 对象首次被实际消费的位置,属于调用栈从入口函数流向命令路径解析层的关键跨越。该函数依据传入 env 中的 PATH 变量逐条搜索目录,将无前缀命令名匹配到第一个存在的可执行文件并返回其绝对路径。当 PATH 被攻击者篡改为指向恶意目录时,此处的解析结果将指向攻击者控制的同名文件,为后续 basename 比对制造虚假吻合。", "file": "src/infra/exec-command-resolution.ts", "line": 51}, {"code": "const resolvedPath = resolveExecutablePath(rawExecutable, cwd, env);", "desc": "在调用栈下游,命令路径解析模块通过此语句取得路径解析函数返回的完整绝对路径,随后调用 path.basename 提取可执行文件的短名称,作为 executableName 向上层 allowlist 评估传递。恶意路径在此处被裁剪为与合法 skill 完全一致的短名称,使后续的集合成员检查无法分辨真实 skill 与同名恶意文件之间的差异,是漏洞利用链中混淆路径身份的关键步骤。", "file": "src/infra/exec-command-resolution.ts", "line": 121}, {"code": "skillAllow = Boolean(params.skillBins?.has(executableName));", "desc": "此赋值语句位于漏洞根因所在的条件分支内部,通过集合成员查询确认 executableName 是否属于已注册的 skill binary 列表,并将结果写入 skillAllow 标志。由于传入的 executableName 来自上游恶意路径的 basename,集合查询将返回肯定结果,skillAllow 被置为 true,评估器将该命令段标记为已通过 skills 白名单,命令在无需操作员确认的情况下进入执行阶段。", "file": "src/infra/exec-approvals-allowlist.ts", "line": 166}, {"code": "const result = await opts.runCommand(", "desc": "漏洞利用链的终点,位于 handleSystemRunInvoke 的命令执行阶段。在 allowlist 评估已被绕过、skillAllow 错误为真后,此处调用 runCommand 正式发起命令执行;实际运行的可执行文件并非受信任 skill 目录下的正版程序,而是攻击者通过 PATH 劫持植入的同名恶意文件。至此,攻击者在操作员毫不知情的情况下完成了本地命令执行,整条利用链闭合。", "file": "src/node-host/invoke-system-run.ts", "line": 465}], "verify": 1, "vuln_category_l1": "业务逻辑", "vuln_category_l2": "BL-AGENT-CAPABILITY(AI/Agent能力边界绕过)", "vuln_ids": ["GHSA-7FF8-XJH3-MGH6"], "vuln_title": "openclaw Incorrect Authorization via basename allowlist matching (GHSA-7ff8-xjh3-mgh6) - exec-approvals-allowlist.ts"} {"commit": "204d9fb404838221df19cc46b30e4cf53209d038", "critical_operation": {"code": "if (", "desc": "evaluateSegments 内的 if 条件块在 allowSkills 为真、resolvedPath 存在、rawExecutable 非空且 !usesExplicitPath 均满足后,调用 skillBins.has(executableName) 做 basename 层面的字符串匹配以决定 skillAllow。此处仅凭名称比对而未验证 resolvedPath 是否属于受信任的 skill 安装目录,是权限绕过漏洞的核心缺陷:路径不受信任的可执行文件可借 basename 碰撞通过此门控。", "file": "src/infra/exec-approvals-allowlist.ts", "line": 159}, "entry_id": "entry-00247", "entry_point": {"code": "export async function handleSystemRunInvoke(opts: HandleSystemRunInvokeOptions): Promise {", "desc": "handleSystemRunInvoke 是处理 system.run 调用的入口函数,接收外部传入的 opts(含 params.command / params.rawCommand),将其解析为可执行命令后启动后续执行流程。外部 argv 在此进入系统,是漏洞链路中可控输入的起点,后续所有 allowlist 评估均以此处解析的命令数据为基础。", "file": "src/node-host/invoke-system-run.ts", "line": 262}, "origin": "GitHub Advisory Database (reviewed)", "project": "openclaw", "repo_url": "https://github.com/openclaw/openclaw", "report_id": "GHSA-7FF8-XJH3-MGH6", "source_link": "https://github.com/advisories/GHSA-7ff8-xjh3-mgh6", "trace": [{"code": "const bins = autoAllowSkills ? await opts.skillBins.current() : new Set();", "desc": "handleSystemRunInvoke 在此处根据 autoAllowSkills 标志决定是否调用 opts.skillBins.current() 获取已注册 skill bin 名称集合,结果赋给 bins,随即作为 skillBins 参数传入 evaluateSystemRunAllowlist。bins 是仅含 basename 字符串的 Set,不携带任何路径信息,这一数据形态是漏洞链路从 caller 流向 callee 时 basename 碰撞可行的根本前提。", "file": "src/node-host/invoke-system-run.ts", "line": 313}, {"code": "function evaluateSystemRunAllowlist(params: {", "desc": "evaluateSystemRunAllowlist 是 handleSystemRunInvoke 调用的中间层函数,接收 shellCommand、argv、skillBins(basename Set)与 autoAllowSkills 等参数,将它们原样转发给底层的 allowlist 评估函数。作为 caller 到 callee 的数据中继,该函数在不对 skillBins 做路径扩充的情况下将不完整的信息透传至漏洞触发层。", "file": "src/node-host/invoke-system-run.ts", "line": 138}, {"code": "function evaluateSegments(", "desc": "evaluateSegments 由 evaluateSystemRunAllowlist 间接调用,接收命令段列表及含 skillBins 与 autoAllowSkills 的参数,在函数入口处计算 allowSkills 标志(须 autoAllowSkills 为 true 且 skillBins 非空同时满足)。该函数是 allowlist 评估的核心执行体,从 caller 传入的 basename Set 在此展开逐段匹配,漏洞的直接触发逻辑位于本函数内部。", "file": "src/infra/exec-approvals-allowlist.ts", "line": 108}, {"code": "const rawExecutable = segment.resolution?.rawExecutable?.trim() ?? \"\";", "desc": "evaluateSegments 在逐段处理时,从 segment.resolution 中提取 rawExecutable(原始命令字符串 trim 后)和 executableName(basename),并通过 isPathScopedExecutableToken 判断 rawExecutable 是否含路径分隔符以设置 usesExplicitPath。这些局部变量为紧随其后的 if 条件块(critical_operation)提供输入:executableName 仅为 basename,是名称碰撞在此处具备可行性的关键因素。", "file": "src/infra/exec-approvals-allowlist.ts", "line": 155}, {"code": "export function resolveCommandResolutionFromArgv(", "desc": "resolveCommandResolutionFromArgv 被上层调用,根据 argv 通过 resolveExecutablePath 解析出 resolvedPath(完整可执行路径),再以 path.basename(resolvedPath) 计算 executableName。当传入相对路径或任意绝对路径时,basename 可与合法 skill bin 完全相同,该函数是 executableName 碰撞的根源,为后续 skillBins.has(executableName) 做出错误判断提供了条件。", "file": "src/infra/exec-command-resolution.ts", "line": 110}, {"code": "skillAllow = Boolean(params.skillBins?.has(executableName));", "desc": "if 条件块通过后,skillAllow 被赋值为 Boolean(params.skillBins?.has(executableName)),以 basename 字符串查询 skillBins Set。当传入的可执行路径其 basename 与已注册 skill bin 名称一致时,has 返回 true,skillAllow 被错误置为 true;缺失对 resolvedPath 是否归属受信任目录的验证,是此处权限绕过的直接根因。", "file": "src/infra/exec-approvals-allowlist.ts", "line": 166}, {"code": "const by: ExecSegmentSatisfiedBy = match", "desc": "ExecSegmentSatisfiedBy 的赋值逻辑按优先级依次检查 match、safe、skillAllow:当前两者为 false 而 skillAllow 为 true 时,by 被置为 \"skills\",segmentSatisfiedBy 记录该段已通过 skills 满足,函数返回 true。由此 allowlistSatisfied 被置为 true,漏洞链路在此完成错误的满足标记,直接导致后续确认提示被跳过。", "file": "src/infra/exec-approvals-allowlist.ts", "line": 168}, {"code": "const requiresAsk = requiresExecApproval({", "desc": "requiresExecApproval 在 ask=on-miss、security=allowlist 配置下评估是否需要操作员确认:当传入的 allowlistSatisfied 为 true 时函数返回 false,requiresAsk 为 false,确认提示不被触发。前序步骤中被错误置位的 allowlistSatisfied 在此直接作用于执行决策,是漏洞链路中绕过确认的最终判定点。", "file": "src/node-host/exec-policy.ts", "line": 85}, {"code": "const result = await opts.runCommand(", "desc": "opts.runCommand 在此以 execArgv、cwd、env 及 timeoutMs 执行命令,由于 requiresAsk 已被前序错误评估置为 false,执行前不存在任何拦截逻辑。这是漏洞链路的终点:basename 碰撞绕过 allowlist 评估、跳过操作员确认后,可执行文件在此被直接运行,完成授权绕过。", "file": "src/node-host/invoke-system-run.ts", "line": 465}], "verify": 1, "vuln_category_l1": "业务逻辑", "vuln_category_l2": "BL-AGENT-CAPABILITY(AI/Agent能力边界绕过)", "vuln_ids": ["GHSA-7FF8-XJH3-MGH6"], "vuln_title": "openclaw Incorrect Authorization via basename allowlist matching (GHSA-7ff8-xjh3-mgh6) - exec-approvals-allowlist.ts"} diff --git a/data/reports.jsonl b/data/reports.jsonl index 6c6d234..e8dcede 100644 --- a/data/reports.jsonl +++ b/data/reports.jsonl @@ -162,7 +162,7 @@ {"commit": "c861862aededc7294cea5634d77e6926444ca101", "entry_ids": ["entry-00391", "entry-00392", "entry-00394", "entry-00395"], "num_entries": 4, "origin": "GitHub Advisory Database (reviewed)", "project": "fastmcp", "repo_url": "https://github.com/PrefectHQ/fastmcp", "report_id": "GHSA-VV7Q-7JX5-F767", "source_link": "https://github.com/advisories/GHSA-vv7q-7jx5-f767", "vuln_ids": ["CVE-2026-32871", "GHSA-VV7Q-7JX5-F767"], "vuln_title": "fastmcp OpenAPIProvider 路径参数未编码导致 SSRF + 路径穿越 (CVE-2026-32871)"} {"commit": "f76f98b26896a7b01f3b2335bb34618c13655b10", "entry_ids": ["entry-00256", "entry-00257"], "num_entries": 2, "origin": "GitHub Advisory Database (reviewed)", "project": "openclaw", "repo_url": "https://github.com/openclaw/openclaw", "report_id": "GHSA-VVJH-F6P9-5VCF", "source_link": "https://github.com/advisories/GHSA-vvjh-f6p9-5vcf", "vuln_ids": ["GHSA-VVJH-F6P9-5VCF"], "vuln_title": "openclaw 网关 canvas 端点 IP 地址认证绕过(CWE-291)"} {"commit": "d4bdcda32403cb8d1f100ae9e27a82f0ffaa04ca", "entry_ids": ["entry-00168", "entry-00169", "entry-00170"], "num_entries": 3, "origin": "GitHub Advisory Database (reviewed)", "project": "openclaw", "repo_url": "https://github.com/openclaw/openclaw", "report_id": "GHSA-W235-X559-36MG", "source_link": "https://github.com/advisories/GHSA-w235-x559-36mg", "vuln_ids": ["CVE-2026-27002", "GHSA-W235-X559-36MG"], "vuln_title": "openclaw Docker 沙箱配置注入 / 容器逃逸(CVE-2026-27002)"} -{"commit": "c56fb7f353d63d6ea97028ee7d8a97bc4edf21c1", "entry_ids": ["entry-00241", "entry-00242", "entry-00243", "entry-00244"], "num_entries": 4, "origin": "GitHub Advisory Database (reviewed)", "project": "openclaw", "repo_url": "https://github.com/openclaw/openclaw", "report_id": "GHSA-W7J5-J98M-W679", "source_link": "https://github.com/advisories/GHSA-w7j5-j98m-w679", "vuln_ids": ["GHSA-W7J5-J98M-W679"], "vuln_title": "OpenClaw has multiple E2E/test Dockerfiles that run all processes as root - Dockerfile"} +{"commit": "c56fb7f353d63d6ea97028ee7d8a97bc4edf21c1", "entry_ids": ["entry-00241", "entry-00242", "entry-00243", "entry-00244"], "num_entries": 4, "origin": "GitHub Advisory Database (reviewed)", "project": "openclaw", "repo_url": "https://github.com/openclaw/openclaw", "report_id": "GHSA-W7J5-J98M-W679", "source_link": "https://github.com/advisories/GHSA-w7j5-j98m-w679", "vuln_ids": ["GHSA-W7J5-J98M-W679"], "vuln_title": "OpenClaw has multiple E2E/test Dockerfiles that run all processes as root"} {"commit": "9942de8011d4b5a141ac507c974c061c0cdad59a", "entry_ids": ["entry-00057"], "num_entries": 1, "origin": "GitHub Advisory Database (reviewed)", "project": "open-webui", "repo_url": "https://github.com/open-webui/open-webui", "report_id": "GHSA-W7XJ-8FX7-WFCH", "source_link": "https://github.com/advisories/GHSA-w7xj-8fx7-wfch", "vuln_ids": ["CVE-2025-64495", "GHSA-W7XJ-8FX7-WFCH"], "vuln_title": "Open WebUI Stored DOM XSS via Prompt Insertion Rich Text Feature"} {"commit": "12dc299cdef3759486b52033e78b550d985c3371", "entry_ids": ["entry-00304", "entry-00305", "entry-00306", "entry-00307"], "num_entries": 4, "origin": "GitHub Advisory Database (reviewed)", "project": "openclaw", "repo_url": "https://github.com/openclaw/openclaw", "report_id": "GHSA-WCXR-59V9-RXR8", "source_link": "https://github.com/advisories/GHSA-wcxr-59v9-rxr8", "vuln_ids": ["CVE-2026-32918", "GHSA-WCXR-59V9-RXR8"], "vuln_title": "OpenClaw session_status sandbox escape via cross-session sessionKey access"} {"commit": "041c47419f5a821fd4adcd46dfc7d85a7eda340e", "entry_ids": ["entry-00362", "entry-00363"], "num_entries": 2, "origin": "GitHub Advisory Database (reviewed)", "project": "openclaw", "repo_url": "https://github.com/openclaw/openclaw", "report_id": "GHSA-WQ58-2PVG-5H4F", "source_link": "https://github.com/advisories/GHSA-wq58-2pvg-5h4f", "vuln_ids": ["CVE-2026-35660", "GHSA-WQ58-2PVG-5H4F"], "vuln_title": "openclaw Gateway Agent RPC Missing Authorization (CWE-862)"} diff --git a/entries.fixed.jsonl b/entries.fixed.jsonl new file mode 100644 index 0000000..5203400 --- /dev/null +++ b/entries.fixed.jsonl @@ -0,0 +1,4 @@ +{"commit":"c56fb7f353d63d6ea97028ee7d8a97bc4edf21c1","critical_operation":{"code":"CMD [\"bash\"]","desc":"CMD 将 bash 设为容器默认启动进程。由于此前没有 USER 指令覆盖基础镜像的 root 默认用户,容器启动时 bash 以 uid=0 运行;进程一旦被控制,攻击者立即取得容器内 root 权限,因此该指令是危险配置在运行时的生效点。","file":"scripts/e2e/Dockerfile","line":23},"entry_id":"entry-00241","entry_point":{"code":"FROM node:22-bookworm","desc":"FROM 声明引入默认用户为 root(uid=0)的 node:22-bookworm 基础镜像,是该 Dockerfile 运行身份配置的起点。检查本文件第 1 至 23 行,未发现 USER 指令覆盖这一默认身份,因此最终启动命令仍使用 root;漏洞来自不安全默认值未被显式降权,而不是普通构建指令之间的污点传播。","file":"scripts/e2e/Dockerfile","line":1},"origin":"GitHub Advisory Database (reviewed)","project":"openclaw","repo_url":"https://github.com/openclaw/openclaw","report_id":"GHSA-W7J5-J98M-W679","source_link":"https://github.com/advisories/GHSA-w7j5-j98m-w679","trace":[],"verify":0,"vuln_category_l1":"特权提升","vuln_category_l2":"容器以不必要特权运行","vuln_ids":["GHSA-W7J5-J98M-W679"],"vuln_title":"OpenClaw has multiple E2E/test Dockerfiles that run all processes as root - Dockerfile"} +{"commit":"c56fb7f353d63d6ea97028ee7d8a97bc4edf21c1","critical_operation":{"code":"RUN pnpm install --frozen-lockfile","desc":"这是文件中最后一个显式执行指令,pnpm 依赖安装及其可能触发的生命周期脚本均以 root 身份运行,从而把第三方构建代码置于不必要的最高权限下。本文件没有 CMD 或 ENTRYPOINT,因此此节点只证明构建阶段的特权执行;不能将它描述为显式的容器运行入口。","file":"scripts/e2e/Dockerfile.qr-import","line":9},"entry_id":"entry-00242","entry_point":{"code":"FROM node:22-bookworm","desc":"FROM 声明引入默认用户为 root(uid=0)的 node:22-bookworm 基础镜像,是本文件权限配置的起点。检查第 1 至 9 行,未发现 USER 指令覆盖该默认身份;该缺失使后续显式构建命令保持 root 权限。","file":"scripts/e2e/Dockerfile.qr-import","line":1},"origin":"GitHub Advisory Database (reviewed)","project":"openclaw","repo_url":"https://github.com/openclaw/openclaw","report_id":"GHSA-W7J5-J98M-W679","source_link":"https://github.com/advisories/GHSA-w7j5-j98m-w679","trace":[],"verify":0,"vuln_category_l1":"特权提升","vuln_category_l2":"容器以不必要特权运行","vuln_ids":["GHSA-W7J5-J98M-W679"],"vuln_title":"OpenClaw has multiple E2E/test Dockerfiles that run all processes as root - Dockerfile.qr-import"} +{"commit":"c56fb7f353d63d6ea97028ee7d8a97bc4edf21c1","critical_operation":{"code":"ENTRYPOINT [\"/usr/local/bin/openclaw-install-e2e\"]","desc":"ENTRYPOINT 将安装脚本设为容器主进程。由于此前没有 USER 指令覆盖基础镜像的 root 默认用户,该脚本在容器启动时以 uid=0 运行;脚本或其调用程序一旦被控制,影响可覆盖整个容器,因此这里是危险配置的运行时生效点。","file":"scripts/docker/install-sh-e2e/Dockerfile","line":14},"entry_id":"entry-00243","entry_point":{"code":"FROM node:22-bookworm-slim","desc":"FROM 声明引入默认用户为 root(uid=0)的 node:22-bookworm-slim 基础镜像,是该 Dockerfile 运行身份配置的起点。检查本文件第 1 至 14 行,未发现 USER 指令覆盖这一默认身份,因此容器入口继承 root;中间的包安装、复制和 chmod 不是独立的权限传播节点。","file":"scripts/docker/install-sh-e2e/Dockerfile","line":1},"origin":"GitHub Advisory Database (reviewed)","project":"openclaw","repo_url":"https://github.com/openclaw/openclaw","report_id":"GHSA-W7J5-J98M-W679","source_link":"https://github.com/advisories/GHSA-w7j5-j98m-w679","trace":[],"verify":0,"vuln_category_l1":"特权提升","vuln_category_l2":"容器以不必要特权运行","vuln_ids":["GHSA-W7J5-J98M-W679"],"vuln_title":"OpenClaw has multiple E2E/test Dockerfiles that run all processes as root - Dockerfile"} +{"commit":"c56fb7f353d63d6ea97028ee7d8a97bc4edf21c1","critical_operation":{"code":" && echo \"app ALL=(ALL) NOPASSWD:ALL\" > /etc/sudoers.d/app","desc":"写入 NOPASSWD:ALL 是核心缺陷:它允许 app 无密码地以 root 或其他任意用户执行任意命令,使第 20 行 USER app 的降权只改变默认 uid,而不能形成有效的最小权限边界。危险来自无限制 sudo 授权,不是第 29 行 ENTRYPOINT 自身以 root 启动。","file":"scripts/docker/install-sh-nonroot/Dockerfile","line":18},"entry_id":"entry-00244","entry_point":{"code":" && echo \"app ALL=(ALL) NOPASSWD:ALL\" > /etc/sudoers.d/app","desc":"该配置声明把 app 暴露为可使用 sudo 的运行身份入口:app 无需凭证即可切换为任意用户并执行任意命令。它不是外部污点输入,而是直接决定后续权限边界的静态配置,因此配置声明本身也是漏洞分析的入口。","file":"scripts/docker/install-sh-nonroot/Dockerfile","line":18},"origin":"GitHub Advisory Database (reviewed)","project":"openclaw","repo_url":"https://github.com/openclaw/openclaw","report_id":"GHSA-W7J5-J98M-W679","source_link":"https://github.com/advisories/GHSA-w7j5-j98m-w679","trace":[{"code":"USER app","desc":"USER app 确实使后续构建步骤和容器默认进程以 app 身份运行,但 app 已在第 18 行获得 NOPASSWD:ALL,因此该切换不能阻止进程按需提升到 root;这是危险配置进入运行身份上下文的生效节点。","file":"scripts/docker/install-sh-nonroot/Dockerfile","line":20},{"code":"RUN sudo chmod +x /usr/local/bin/openclaw-install-nonroot","desc":"该命令在 USER app 之后成功通过 sudo 执行需要高权限的文件操作,直接证明第 18 行免密 sudo 配置可用。它是配置生效证据,不表示最终 ENTRYPOINT 会自动以 root 运行。","file":"scripts/docker/install-sh-nonroot/Dockerfile","line":27}],"verify":0,"vuln_category_l1":"特权提升","vuln_category_l2":"容器以不必要特权运行","vuln_ids":["GHSA-W7J5-J98M-W679"],"vuln_title":"OpenClaw has multiple E2E/test Dockerfiles that run all processes as root - Dockerfile"} From ab64a51e203311c2b37a61a9d39e88c6a4602402 Mon Sep 17 00:00:00 2001 From: Chenghao Date: Sun, 26 Jul 2026 17:50:13 -0400 Subject: [PATCH 2/2] =?UTF-8?q?=E8=8A=82=E7=82=B9=E4=BD=8D=E7=BD=AE?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E4=BF=AE=E5=A4=8Dissue4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FIX_ENTRY_LOCATIONS.md | 51 +++ entries.fixed.jsonl | 412 +++++++++++++++++++- fix_diff.csv | 110 ++++++ issue4.md | 50 +++ needs_human.csv | 95 +++++ scripts/fix_entry_locations.py | 557 ++++++++++++++++++++++++++++ scripts/test_fix_entry_locations.py | 136 +++++++ 7 files changed, 1407 insertions(+), 4 deletions(-) create mode 100644 FIX_ENTRY_LOCATIONS.md create mode 100644 fix_diff.csv create mode 100644 issue4.md create mode 100644 needs_human.csv create mode 100644 scripts/fix_entry_locations.py create mode 100644 scripts/test_fix_entry_locations.py diff --git a/FIX_ENTRY_LOCATIONS.md b/FIX_ENTRY_LOCATIONS.md new file mode 100644 index 0000000..ad124a9 --- /dev/null +++ b/FIX_ENTRY_LOCATIONS.md @@ -0,0 +1,51 @@ +# Entry 节点位置自动修复 + +`scripts/fix_entry_locations.py` 根据节点的 `code` 检查并保守修复 +`entry_point`、`critical_operation` 和 `trace[*]` 的文件及行号。 + +## 运行方式 + +从仓库根目录执行完整检查: + +```bash +python3 scripts/fix_entry_locations.py +``` + +默认生成: + +- `entries.fixed.jsonl`:保留全部 entry,只修改唯一确定的节点; +- `fix_diff.csv`:自动修改前后的字段和采用的策略; +- `needs_human.csv`:多处命中、找不到或源码不可用的节点; +- `.cache/vulngym-source/`:按仓库复用的 Git 源码缓存。 + +只检查未验证数据或指定样本: + +```bash +python3 scripts/fix_entry_locations.py --only-unverified +python3 scripts/fix_entry_locations.py --entry-id entry-00103 --entry-id entry-00320 +``` + +已有完整缓存时可使用 `--offline`,确保运行期间不访问网络。其他路径可通过 +`--input`、`--output`、`--diff`、`--needs-human` 和 `--cache-dir` 修改。 + +## 修复策略 + +脚本先将每行代码去除首尾空白并折叠连续空白,从而容忍缩进及 tab/空格差异, +同时按连续的非空代码行匹配多行片段。匹配失败后依次尝试: + +1. 原行号上下 5 行内唯一命中; +2. 原文件内唯一命中; +3. 整个 commit 中唯一命中。 + +只有唯一命中才会修改。路径或行号变化后,脚本会同步更新 `desc` 中明确出现的 +旧路径和行号引用。所有修改写入 `fix_diff.csv`。 + +多处命中、完全找不到、代码过短而无法安全搜索、候选范围过大或源码获取失败时, +节点保持原样并写入 `needs_human.csv`。 + +## 限制 + +- 脚本只修复位置以及 `desc` 中明确的旧位置引用,不自动重写漏洞语义; +- 极短片段(例如单独的 `}`)通常无法支持可靠的跨仓库定位; +- 首次运行需要下载目标 commit,速度取决于网络和仓库大小;后续运行会复用缓存; +- `needs_human.csv` 中的结果必须由人工结合漏洞上下文判断。 diff --git a/entries.fixed.jsonl b/entries.fixed.jsonl index 5203400..6b94bb5 100644 --- a/entries.fixed.jsonl +++ b/entries.fixed.jsonl @@ -1,4 +1,408 @@ -{"commit":"c56fb7f353d63d6ea97028ee7d8a97bc4edf21c1","critical_operation":{"code":"CMD [\"bash\"]","desc":"CMD 将 bash 设为容器默认启动进程。由于此前没有 USER 指令覆盖基础镜像的 root 默认用户,容器启动时 bash 以 uid=0 运行;进程一旦被控制,攻击者立即取得容器内 root 权限,因此该指令是危险配置在运行时的生效点。","file":"scripts/e2e/Dockerfile","line":23},"entry_id":"entry-00241","entry_point":{"code":"FROM node:22-bookworm","desc":"FROM 声明引入默认用户为 root(uid=0)的 node:22-bookworm 基础镜像,是该 Dockerfile 运行身份配置的起点。检查本文件第 1 至 23 行,未发现 USER 指令覆盖这一默认身份,因此最终启动命令仍使用 root;漏洞来自不安全默认值未被显式降权,而不是普通构建指令之间的污点传播。","file":"scripts/e2e/Dockerfile","line":1},"origin":"GitHub Advisory Database (reviewed)","project":"openclaw","repo_url":"https://github.com/openclaw/openclaw","report_id":"GHSA-W7J5-J98M-W679","source_link":"https://github.com/advisories/GHSA-w7j5-j98m-w679","trace":[],"verify":0,"vuln_category_l1":"特权提升","vuln_category_l2":"容器以不必要特权运行","vuln_ids":["GHSA-W7J5-J98M-W679"],"vuln_title":"OpenClaw has multiple E2E/test Dockerfiles that run all processes as root - Dockerfile"} -{"commit":"c56fb7f353d63d6ea97028ee7d8a97bc4edf21c1","critical_operation":{"code":"RUN pnpm install --frozen-lockfile","desc":"这是文件中最后一个显式执行指令,pnpm 依赖安装及其可能触发的生命周期脚本均以 root 身份运行,从而把第三方构建代码置于不必要的最高权限下。本文件没有 CMD 或 ENTRYPOINT,因此此节点只证明构建阶段的特权执行;不能将它描述为显式的容器运行入口。","file":"scripts/e2e/Dockerfile.qr-import","line":9},"entry_id":"entry-00242","entry_point":{"code":"FROM node:22-bookworm","desc":"FROM 声明引入默认用户为 root(uid=0)的 node:22-bookworm 基础镜像,是本文件权限配置的起点。检查第 1 至 9 行,未发现 USER 指令覆盖该默认身份;该缺失使后续显式构建命令保持 root 权限。","file":"scripts/e2e/Dockerfile.qr-import","line":1},"origin":"GitHub Advisory Database (reviewed)","project":"openclaw","repo_url":"https://github.com/openclaw/openclaw","report_id":"GHSA-W7J5-J98M-W679","source_link":"https://github.com/advisories/GHSA-w7j5-j98m-w679","trace":[],"verify":0,"vuln_category_l1":"特权提升","vuln_category_l2":"容器以不必要特权运行","vuln_ids":["GHSA-W7J5-J98M-W679"],"vuln_title":"OpenClaw has multiple E2E/test Dockerfiles that run all processes as root - Dockerfile.qr-import"} -{"commit":"c56fb7f353d63d6ea97028ee7d8a97bc4edf21c1","critical_operation":{"code":"ENTRYPOINT [\"/usr/local/bin/openclaw-install-e2e\"]","desc":"ENTRYPOINT 将安装脚本设为容器主进程。由于此前没有 USER 指令覆盖基础镜像的 root 默认用户,该脚本在容器启动时以 uid=0 运行;脚本或其调用程序一旦被控制,影响可覆盖整个容器,因此这里是危险配置的运行时生效点。","file":"scripts/docker/install-sh-e2e/Dockerfile","line":14},"entry_id":"entry-00243","entry_point":{"code":"FROM node:22-bookworm-slim","desc":"FROM 声明引入默认用户为 root(uid=0)的 node:22-bookworm-slim 基础镜像,是该 Dockerfile 运行身份配置的起点。检查本文件第 1 至 14 行,未发现 USER 指令覆盖这一默认身份,因此容器入口继承 root;中间的包安装、复制和 chmod 不是独立的权限传播节点。","file":"scripts/docker/install-sh-e2e/Dockerfile","line":1},"origin":"GitHub Advisory Database (reviewed)","project":"openclaw","repo_url":"https://github.com/openclaw/openclaw","report_id":"GHSA-W7J5-J98M-W679","source_link":"https://github.com/advisories/GHSA-w7j5-j98m-w679","trace":[],"verify":0,"vuln_category_l1":"特权提升","vuln_category_l2":"容器以不必要特权运行","vuln_ids":["GHSA-W7J5-J98M-W679"],"vuln_title":"OpenClaw has multiple E2E/test Dockerfiles that run all processes as root - Dockerfile"} -{"commit":"c56fb7f353d63d6ea97028ee7d8a97bc4edf21c1","critical_operation":{"code":" && echo \"app ALL=(ALL) NOPASSWD:ALL\" > /etc/sudoers.d/app","desc":"写入 NOPASSWD:ALL 是核心缺陷:它允许 app 无密码地以 root 或其他任意用户执行任意命令,使第 20 行 USER app 的降权只改变默认 uid,而不能形成有效的最小权限边界。危险来自无限制 sudo 授权,不是第 29 行 ENTRYPOINT 自身以 root 启动。","file":"scripts/docker/install-sh-nonroot/Dockerfile","line":18},"entry_id":"entry-00244","entry_point":{"code":" && echo \"app ALL=(ALL) NOPASSWD:ALL\" > /etc/sudoers.d/app","desc":"该配置声明把 app 暴露为可使用 sudo 的运行身份入口:app 无需凭证即可切换为任意用户并执行任意命令。它不是外部污点输入,而是直接决定后续权限边界的静态配置,因此配置声明本身也是漏洞分析的入口。","file":"scripts/docker/install-sh-nonroot/Dockerfile","line":18},"origin":"GitHub Advisory Database (reviewed)","project":"openclaw","repo_url":"https://github.com/openclaw/openclaw","report_id":"GHSA-W7J5-J98M-W679","source_link":"https://github.com/advisories/GHSA-w7j5-j98m-w679","trace":[{"code":"USER app","desc":"USER app 确实使后续构建步骤和容器默认进程以 app 身份运行,但 app 已在第 18 行获得 NOPASSWD:ALL,因此该切换不能阻止进程按需提升到 root;这是危险配置进入运行身份上下文的生效节点。","file":"scripts/docker/install-sh-nonroot/Dockerfile","line":20},{"code":"RUN sudo chmod +x /usr/local/bin/openclaw-install-nonroot","desc":"该命令在 USER app 之后成功通过 sudo 执行需要高权限的文件操作,直接证明第 18 行免密 sudo 配置可用。它是配置生效证据,不表示最终 ENTRYPOINT 会自动以 root 运行。","file":"scripts/docker/install-sh-nonroot/Dockerfile","line":27}],"verify":0,"vuln_category_l1":"特权提升","vuln_category_l2":"容器以不必要特权运行","vuln_ids":["GHSA-W7J5-J98M-W679"],"vuln_title":"OpenClaw has multiple E2E/test Dockerfiles that run all processes as root - Dockerfile"} +{"commit": "9942de8011d4b5a141ac507c974c061c0cdad59a", "critical_operation": {"code": "tempDiv.innerHTML = htmlContent;", "desc": "tempDiv.innerHTML = htmlContent 将 marked.parse() 输出的 HTML 字符串直接赋值给 DOM 节点,使浏览器对其内容进行完整解析;此赋值前缺少净化处理,当 htmlContent 携带可执行脚本时,浏览器在此完成脚本注入,是整条链路的最终利用点。", "file": "src/lib/components/common/RichTextInput.svelte", "line": 348}, "entry_id": "entry-00057", "entry_point": {"code": "insertTextHandler(data.content);", "desc": "insertTextHandler 以 data.content 为实参被调用,将补全列表中用户选中的提示词 content 字段注入插入处理流程;data.content 直接源自后端持久化的提示词记录,在此处未经任何过滤便向下游传递,构成整条漏洞利用链的触发起点。", "file": "src/lib/components/chat/MessageInput/CommandSuggestionList.svelte", "line": 97}, "origin": "GitHub Advisory Database (reviewed)", "project": "open-webui", "repo_url": "https://github.com/open-webui/open-webui", "report_id": "GHSA-W7XJ-8FX7-WFCH", "source_link": "https://github.com/advisories/GHSA-w7xj-8fx7-wfch", "trace": [{"code": "insertTextHandler(data.content);", "desc": "insertTextHandler(data.content) 作为漏洞传播链的起始节点,在用户选中补全项后立即被触发;data.content 携带从 store 中取出的原始提示词内容,未经校验即向调用栈下游转发,开启后续富文本渲染流程。", "file": "src/lib/components/chat/MessageInput/CommandSuggestionList.svelte", "line": 97}, {"code": "@router.post(\"/create\", response_model=Optional[PromptModel])", "desc": "@router.post(\"/create\") 注册提示词创建接口,授权用户可通过此端点向数据库写入任意 content 字段;恶意构造的 HTML 字符串经此接口被持久化存储,成为前端后续加载与渲染时 XSS 的载荷来源,是漏洞链路中载荷写入的持久化节点。", "file": "backend/open_webui/routers/prompts.py", "line": 47}, {"code": "prompts.set(await getPrompts(localStorage.token));", "desc": "getPrompts 向后端请求全量提示词列表,返回结果随即经 prompts.set 写入 Svelte store;由前一步持久化的恶意 content 在此步骤被拉取至前端内存,使恶意条目在补全列表中变为可被用户选中的状态。", "file": "src/lib/components/chat/MessageInput/CommandSuggestionList.svelte", "line": 30}, {"code": "onSelect={(e) => {", "desc": "onSelect 回调在用户从补全列表中点选提示词条目时被激活;该处理函数接收选中项并将其数据向 insertTextHandler 方向传递,将界面操作转化为内容插入调用,是交互层与插入逻辑之间的衔接节点。", "file": "src/lib/components/chat/MessageInput/CommandSuggestionList.svelte", "line": 93}, {"code": "const replaceCommandWithText = (text) => {", "desc": "replaceCommandWithText 定义了将斜杠命令占位符替换为提示词正文的处理逻辑,接收由 onSelect 向下传递的 text 参数;随后该函数将内容路由至 RichTextInput 的 insertTextHandler,使原始字符串在未经净化的情况下继续向渲染层流动。", "file": "src/lib/components/chat/MessageInput.svelte", "line": 324}, {"code": "insertPromptAsRichText={$settings?.insertPromptAsRichText ?? false}", "desc": "insertPromptAsRichText 属性将 $settings.insertPromptAsRichText 配置值以 prop 形式向 RichTextInput 组件注入;该布尔标志直接控制下游是否进入 innerHTML 赋值分支,当用户启用此选项时,在调用栈下游的 HTML 解析路径随即被激活。", "file": "src/lib/components/chat/MessageInput.svelte", "line": 1207}, {"code": "if (insertPromptAsRichText) {", "desc": "if (insertPromptAsRichText) 依据由前一步传入的 prop 值决定执行路径;条件为真时进入富文本插入分支,后续代码将对 htmlContent 执行 innerHTML 赋值,该判断是安全路径与危险路径的唯一分叉点,直接决定漏洞利用路径是否开启。", "file": "src/lib/components/common/RichTextInput.svelte", "line": 338}, {"code": "const tempDiv = document.createElement('div');\n\t\t\ttempDiv.innerHTML = htmlContent;", "desc": "document.createElement('div') 创建临时节点,随后 tempDiv.innerHTML 直接接收 htmlContent 字符串并触发浏览器 HTML 解析;该赋值前未调用任何净化函数,含有脚本标签的内容在此被执行,完成由持久化写入、前端加载、用户触发逐步推进的存储型 XSS 链路终点。", "file": "src/lib/components/common/RichTextInput.svelte", "line": "347-348"}], "verify": 1, "vuln_category_l1": "XSS", "vuln_category_l2": "Stored XSS", "vuln_ids": ["CVE-2025-64495", "GHSA-W7XJ-8FX7-WFCH"], "vuln_title": "Open WebUI Stored DOM XSS via Prompt Insertion Rich Text Feature"} +{"commit": "2fad5b34f7d3cf44cf0436ae7f1f31fabf17b6a0", "critical_operation": {"code": "\t\tif !validSourceID(ctx, md[strings.ToLower(util.HeaderSourceID)]) {", "desc": "validSourceID(ctx, md[strings.ToLower(util.HeaderSourceID)]) 的返回值在此直接控制认证分支走向:返回 true 则 if 块体不执行,用户名/密码及 API Key 的全部校验被跳过;返回 false 才进入凭证验证。这是认证绕过的核心控制点——sourceId 头的值未经网络来源约束即被当作可信内部成员标识。", "file": "internal/proxy/authentication_interceptor.go", "line": 84}, "entry_id": "entry-00058", "entry_point": {"code": "\t\t\tproxy.GrpcAuthInterceptor(proxy.AuthenticationInterceptor),", "desc": "proxy.GrpcAuthInterceptor(proxy.AuthenticationInterceptor) 在此以函数调用形式出现,将认证函数 AuthenticationInterceptor 封装后挂入 gRPC UnaryInterceptor 链。这是漏洞链的注册入口:所有入站 gRPC 请求经过此位置后,后续中间件执行序列将必然触达认证逻辑,使 validSourceID 分支成为可利用路径。", "file": "internal/distributed/proxy/service.go", "line": 245}, "origin": "GitHub Advisory Database (reviewed)", "project": "milvus-io/milvus", "repo_url": "https://github.com/milvus-io/milvus", "report_id": "GHSA-MHJQ-8C7M-3F7P", "source_link": "https://github.com/advisories/GHSA-mhjq-8c7m-3f7p", "trace": [{"code": "\tif enableCustomInterceptor {\n\t\tunaryServerOption = grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(\n\t\t\tproxy.DatabaseInterceptor(),\n\t\t\tUnaryRequestStatsInterceptor,\n\t\t\taccesslog.UnaryAccessLogInterceptor,\n\t\t\tproxy.GrpcAuthInterceptor(proxy.AuthenticationInterceptor),\n\t\t\tproxy.UnaryServerHookInterceptor(),\n\t\t\tproxy.UnaryServerInterceptor(proxy.PrivilegeInterceptor),\n\t\t\tlogutil.UnaryTraceLoggerInterceptor,\n\t\t\tproxy.RateLimitInterceptor(limiter),", "desc": "此段从 gRPC 服务初始化(caller)流向中间件链组装(callee):enableCustomInterceptor 为 true 时,ChainUnaryServer 按序构建包含数据库、日志、认证等拦截器的 UnaryInterceptor 链,proxy.GrpcAuthInterceptor(proxy.AuthenticationInterceptor) 位于第四位。这是漏洞链的起点,认证函数在此被纳入所有入站 gRPC 请求的必经路径。", "file": "internal/distributed/proxy/service.go", "line": "240-249"}, {"code": "func GrpcAuthInterceptor(authFunc grpc_auth.AuthFunc) grpc.UnaryServerInterceptor {\n\treturn func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {\n\t\tvar newCtx context.Context\n\t\tvar err error\n\t\tif overrideSrv, ok := info.Server.(grpc_auth.ServiceAuthFuncOverride); ok {\n\t\t\tnewCtx, err = overrideSrv.AuthFuncOverride(ctx, info.FullMethod)\n\t\t} else {\n\t\t\tnewCtx, err = authFunc(ctx)\n\t\t}\n\t\tif err != nil {", "desc": "GrpcAuthInterceptor 从 caller(trace[0] 的注册调用)接收 AuthenticationInterceptor 作为 authFunc,将其包装为 grpc.UnaryServerInterceptor 闭包返回。请求到达时,该闭包调用 authFunc(ctx) 进入 AuthenticationInterceptor 的具体逻辑(即 trace[4] 所示代码块),完成从拦截器注册到认证函数执行的流转。", "file": "internal/proxy/authentication_interceptor.go", "line": "50-59"}, {"code": "\t\treturn false\n\t}\n\t// token format: base64\n\ttoken := authorization[0]\n\tsourceID, err := crypto.Base64Decode(token)\n\tif err != nil {\n\t\treturn false\n\t}\n\treturn sourceID == util.MemberCredID\n}", "desc": "validSourceID 的末尾执行路径从 caller(trace[4] 中的条件判断)流入此处:取 authorization[0] 经 crypto.Base64Decode 解码为 sourceID,再与 util.MemberCredID 做字符串比较并返回结果。解码逻辑本身无误,但 MemberCredID 是硬编码的已知常量(见 trace[3]),任何能构造请求的客户端均可提前知晓并伪造匹配值。", "file": "internal/proxy/authentication_interceptor.go", "line": "39-48"}, {"code": "\t// HeaderSourceID identify requests from Milvus members and client requests\n\tHeaderSourceID = \"sourceId\"\n\t// MemberCredID id for Milvus members (data/index/query node/coord component)\n\tMemberCredID = \"@@milvus-member@@\"", "desc": "HeaderSourceID(\"sourceId\")与 MemberCredID(\"@@milvus-member@@\")在此定义,分别被 trace[4] 的 AuthenticationInterceptor 用于定位 gRPC metadata 头名称、被 trace[2] 的 validSourceID 用于比对解码结果。两个值均公开可查,任何外部调用方均可构造满足条件的 sourceId 头,无需内部凭证。", "file": "pkg/util/constant.go", "line": "49-52"}, {"code": "\tif Params.CommonCfg.AuthorizationEnabled.GetAsBool() {\n\t\tif !validSourceID(ctx, md[strings.ToLower(util.HeaderSourceID)]) {\n\t\t\tauthStrArr := md[strings.ToLower(util.HeaderAuthorize)]\n\n\t\t\tif len(authStrArr) < 1 {\n\t\t\t\tlog.Warn(\"key not found in header\")\n\t\t\t\treturn nil, status.Error(codes.Unauthenticated, \"missing authorization in header\")\n\t\t\t}\n\n\t\t\t// token format: base64", "desc": "AuthenticationInterceptor 从 caller(GrpcAuthInterceptor 闭包,即 trace[1])接收 ctx 后,首先确认 AuthorizationEnabled 为 true,随即调用 validSourceID(trace[2])检查 sourceId 头;若返回 true 则认证通过、跳过凭证验证块;若返回 false 则提取 HeaderAuthorize 执行实际凭证校验。这是 critical_operation 所在的直接承载函数体。", "file": "internal/proxy/authentication_interceptor.go", "line": "83-92"}], "verify": 1, "vuln_category_l1": "业务逻辑", "vuln_category_l2": "BL-AUTH-BYPASS(身份认证绕过)", "vuln_ids": ["CVE-2025-64513", "GHSA-MHJQ-8C7M-3F7P"], "vuln_title": "Milvus Proxy sourceID 头认证绕过 (CVE-2025-64513)"} +{"commit": "1ae1638ed972bcc913611ae9268a972d0ae127ec", "critical_operation": {"code": "const saveProfileResp = await userApi.updateUser(obj)", "desc": "userApi.updateUser 以 await 形式将携带未经验证新邮箱的 obj 异步提交后端持久化接口;后端在缺乏前置身份再验证的情况下直接执行邮箱字段覆写,是整条漏洞链路中污点数据从前端 state 落地为数据库记录的关键操作节点。", "file": "packages/ui/src/views/account/index.jsx", "line": 205}, "entry_id": "entry-00061", "entry_point": {"code": "onChange={(e) => setEmail(e.target.value)}", "desc": "onChange 回调将邮箱输入框中每次击键产生的任意字符串通过 setEmail 直接写入 email state,不经格式校验或身份授权判断;该未受管控的 state 赋值是整条账号接管链路的污点入口,为后续无二次验证的更新请求提供了原始用户输入来源。", "file": "packages/ui/src/views/account/index.jsx", "line": 702}, "origin": "GitHub Advisory Database (reviewed)", "project": "FlowiseAI/Flowise", "repo_url": "https://github.com/FlowiseAI/Flowise", "report_id": "GHSA-X39M-3393-3QP4", "source_link": "https://github.com/advisories/GHSA-x39m-3393-3qp4", "trace": [{"code": "onChange={(e) => setEmail(e.target.value)}", "desc": "onChange 事件处理器是调用链的起点,在用户每次修改邮箱输入时将任意新值即时赋入 email state,全程不触发校验;污点数据从此进入前端状态树,随后等待用户点击保存按钮时向 saveProfileData 传播。", "file": "packages/ui/src/views/account/index.jsx", "line": 702}, {"code": "", "desc": "保存按钮通过 onClick 属性绑定 saveProfileData,用户点击时触发从 UI 渲染层到业务逻辑层的控制流跳转;此跳转节点未设置任何二次身份确认门控,使 email state 中的污点值得以无阻流入提交函数,随后被打包进更新请求。", "file": "packages/ui/src/views/account/index.jsx", "line": 667}, {"code": "const saveProfileData = async () => {", "desc": "saveProfileData 声明了承接按钮点击事件的异步提交函数入口;在其函数体中将直接读取前端 email state 并构造含新邮箱的更新载荷,全程不设当前用户身份重验证步骤,是漏洞根因所在的函数边界,调用栈下游的所有操作均从此触发。", "file": "packages/ui/src/views/account/index.jsx", "line": 198}, {"code": " const obj = {\n id: currentUser.id,\n name: profileName,\n email: email\n }\n const saveProfileResp = await userApi.updateUser(obj)\n if (saveProfileResp.data) {", "desc": "函数体内将 currentUser.id、profileName 与未经验证的 email state 合并构造 obj 载荷,随即以 await 形式调用 updateUser 提交后端;整段代码既无密码再验证字段也无确认令牌,提供了将账号邮箱替换为任意地址的完整执行路径。", "file": "packages/ui/src/views/account/index.jsx", "line": "200-206"}, {"code": "const saveProfileResp = await userApi.updateUser(obj)", "desc": "saveProfileData 调用链在此收束,updateUser 接收含任意新邮箱的 obj 并发起异步持久化请求;后端直接覆写邮箱字段,使污点从前端 state 经全链路无验证传播最终写入数据库,账号接管由此完成。", "file": "packages/ui/src/views/account/index.jsx", "line": 205}], "verify": 1, "vuln_category_l1": "业务逻辑", "vuln_category_l2": "BL-WORKFLOW-VIOLATION(业务流程/状态机违规)", "vuln_ids": ["GHSA-X39M-3393-3QP4"], "vuln_title": "flowise-ui 未验证邮箱变更导致账号接管"} +{"commit": "6cdb13d5cb26b7bceebfe0b85723d8a61aebae3b", "critical_operation": {"code": " async with session.get(", "desc": "SafeWebBaseLoader 内部经由 aiohttp 客户端会话发起真实 HTTP GET 请求,目标地址即来自上游未经充分过滤的 url;此处是整条 SSRF 利用链的最终执行点,服务器将主动向攻击者指定的任意地址发送网络请求并返回响应内容。", "file": "backend/open_webui/retrieval/web/utils.py", "line": 556}, "entry_id": "entry-00062", "entry_point": {"code": " request: Request, form_data: ProcessUrlForm, user=Depends(get_verified_user)", "desc": "process_web 路由函数的参数声明,通过 ProcessUrlForm 接收用户提交的 url 字段;get_verified_user 仅要求已登录账户,无需管理员权限。外部可控的 url 从此处进入漏洞链路,是 SSRF 攻击的起始注入点。", "file": "backend/open_webui/routers/retrieval.py", "line": 1778}, "origin": "GitHub Advisory Database (reviewed)", "project": "open-webui", "repo_url": "https://github.com/open-webui/open-webui", "report_id": "GHSA-C6XV-RCVW-V685", "source_link": "https://github.com/advisories/GHSA-c6xv-rcvw-v685", "trace": [{"code": " request: Request, form_data: ProcessUrlForm, user=Depends(get_verified_user)", "desc": "漏洞链路的入口:process_web 函数通过 ProcessUrlForm 接收外部提交的 url,认证仅要求普通已登录用户(get_verified_user),无管理员限制。用户可控的 url 从此步骤开始流向下游调用栈,后续各环节均未对其内容实施足够的安全过滤。", "file": "backend/open_webui/routers/retrieval.py", "line": 1778}, {"code": " content, docs = get_content_from_url(request, form_data.url)", "desc": "随后,process_web 函数将 form_data.url 直接传入 get_content_from_url(),此处是 caller 向 callee 传递未经清洗的 url 参数的关键跳转点;url 在无任何修改或过滤的情况下从路由层流向内容获取逻辑层,污点沿调用边扩散。", "file": "backend/open_webui/routers/retrieval.py", "line": 1785}, {"code": "def get_content_from_url(request, url: str) -> str:\n loader = get_loader(request, url)\n docs = loader.load()\n content = \" \".join([doc.page_content for doc in docs])\n return content, docs", "desc": "在调用栈下游,get_content_from_url 函数接收来自上游的 url,将其转发给 get_loader() 以获取加载器实例,再调用 load() 执行实际内容抓取;该函数本身不作任何安全校验,充当 url 向加载器层传递的中间桥接,污点继续向下传播。", "file": "backend/open_webui/retrieval/utils.py", "line": "78-82"}, {"code": "def get_loader(request, url: str):\n if is_youtube_url(url):\n return YoutubeLoader(\n url,\n language=request.app.state.config.YOUTUBE_LOADER_LANGUAGE,\n proxy_url=request.app.state.config.YOUTUBE_LOADER_PROXY_URL,\n )\n else:\n return get_web_loader(\n url,", "desc": "随后 get_loader 函数判断 url 是否为 YouTube 链接:若否则调用 get_web_loader(),将 url 从通用加载器调度层传递到专用的 Web 加载器层;此分支路由决定了后续验证逻辑的具体路径,非 YouTube 的任意 url 均进入含缺陷校验的 get_web_loader 调用链。", "file": "backend/open_webui/retrieval/utils.py", "line": "62-71"}, {"code": "def get_web_loader(\n urls: Union[str, Sequence[str]],\n verify_ssl: bool = True,\n requests_per_second: int = 2,\n trust_env: bool = False,\n):\n # Check if the URLs are valid\n safe_urls = safe_validate_urls([urls] if isinstance(urls, str) else urls)", "desc": "在调用栈下游,get_web_loader 接收 url 后立即调用 safe_validate_urls() 执行批量验证,将字符串 url 包装成列表后传入;此处是 caller 向 URL 验证层传递的边界,但验证逻辑存在缺陷,通过验证的 url 随后将被用于构造 SafeWebBaseLoader 实例并触发真实网络请求。", "file": "backend/open_webui/retrieval/web/utils.py", "line": "636-643"}, {"code": "def safe_validate_urls(url: Sequence[str]) -> Sequence[str]:\n valid_urls = []\n for u in url:\n try:\n if validate_url(u):\n valid_urls.append(u)\n except Exception as e:\n log.debug(f\"Invalid URL {u}: {str(e)}\")\n continue\n return valid_urls", "desc": "safe_validate_urls 对每个 url 逐一调用 validate_url() 并用 try/except 捕获所有异常;一旦 validate_url 抛出异常(如 DNS 解析失败),该 url 被静默丢弃而非拦截,但当 url 恰好通过格式检查而 validate_url 仅返回真值时,含缺陷的 url 仍被加入结果列表继续下传。", "file": "backend/open_webui/retrieval/web/utils.py", "line": "73-82"}, {"code": "def validate_url(url: Union[str, Sequence[str]]):\n if isinstance(url, str):\n if isinstance(validators.url(url), validators.ValidationError):\n raise ValueError(ERROR_MESSAGES.INVALID_URL)\n if not ENABLE_RAG_LOCAL_WEB_FETCH:\n # Local web fetch is disabled, filter out any URLs that resolve to private IP addresses\n parsed_url = urllib.parse.urlparse(url)\n # Get IPv4 and IPv6 addresses\n ipv4_addresses, ipv6_addresses = resolve_hostname(parsed_url.hostname)\n # Check if any of the resolved addresses are private", "desc": "validate_url 对 url 进行格式校验,并在 ENABLE_RAG_LOCAL_WEB_FETCH 关闭时解析主机名以检查是否落入私有 IP 段;然而该函数缺少对云厂商元数据服务地址(如 169.254.169.254 等链路本地地址及特定主机名)的显式阻断名单,是整条链路中安全防护的核心缺口所在。", "file": "backend/open_webui/retrieval/web/utils.py", "line": "49-58"}, {"code": " async with session.get(\n url,\n **(self.requests_kwargs | kwargs),\n allow_redirects=False,\n ) as response:\n if self.raise_for_status:\n response.raise_for_status()\n return await response.text()", "desc": "经过上游缺陷验证的 url 最终抵达此处:SafeWebBaseLoader 通过 aiohttp 的异步 session.get() 向目标地址发起 HTTP GET 请求,allow_redirects=False 仅阻止跟随重定向,但不影响对原始 url 的直接访问;响应体通过 response.text() 读取后逐层返回给调用方,服务端请求伪造在此完成闭环。", "file": "backend/open_webui/retrieval/web/utils.py", "line": "556-563"}], "verify": 1, "vuln_category_l1": "注入", "vuln_category_l2": "服务端请求伪造", "vuln_ids": ["CVE-2025-65958", "GHSA-C6XV-RCVW-V685"], "vuln_title": "Open WebUI SSRF via /api/v1/retrieval/process/web 缺失云元数据端点拦截 (CVE-2025-65958)"} +{"commit": "7c803f4e0bd9e7a794a4ef2a2b45e58adff61a50", "critical_operation": {"code": " switch (platform) {\n case Platform.OPEN_SOURCE:\n data.organization.name = OrganizationName.DEFAULT_ORGANIZATION\n data.organizationUser.role = await this.roleService.readGeneralRoleByName(GeneralRole.OWNER, queryRunner)\n data.workspace.name = WorkspaceName.DEFAULT_WORKSPACE\n data.workspaceUser.role = data.organizationUser.role\n data.user.status = UserStatus.ACTIVE\n data.user = await this.userService.createNewUser(data.user, queryRunner)", "desc": "switch(platform) 分支在 Platform.OPEN_SOURCE 路径下无条件将组织用户角色与工作区用户角色均赋予 OWNER,将账户状态置为 ACTIVE,随后调用 createNewUser 完成持久化写入。整个分支不存在对已有账户数量或系统初始化状态的任何前置校验,是来自未认证入口的注册请求在无阻碍传递后最终完成 OWNER 级账户创建的关键执行位置。", "file": "packages/server/src/enterprise/services/account.service.ts", "line": "112-119"}, "entry_id": "entry-00063", "entry_point": {"code": "router.post('/register', accountController.register)", "desc": "路由层通过 Express router.post 将 POST /register 路径与 accountController.register 处理函数绑定,形成外部 HTTP 请求进入注册业务逻辑的入口节点。此绑定本身不附加任何认证中间件,意味着该端点能否受到访问控制完全取决于全局中间件配置;结合白名单中的对应条目,该路由成为未认证请求可直接触达注册逻辑的源头。", "file": "packages/server/src/enterprise/routes/account.route.ts", "line": 9}, "origin": "GitHub Advisory Database (reviewed)", "project": "Flowise (FlowiseAI/Flowise)", "repo_url": "https://github.com/FlowiseAI/Flowise", "report_id": "GHSA-V5W9-PRXF-W882", "source_link": "https://github.com/advisories/GHSA-v5w9-prxf-w882", "trace": [{"code": "router.post('/register', accountController.register)", "desc": "从调用链起点,account.route.ts 中 router.post 语句将 POST /register 端点与 accountController.register 处理函数绑定;外部请求到达时,执行权从路由层流向控制器层,完成从网络入口到业务逻辑的第一段跨越。该路由注册不附加局部认证守卫,后续请求是否受保护完全由全局中间件决定。", "file": "packages/server/src/enterprise/routes/account.route.ts", "line": 9}, {"code": " '/api/v1/account/register',", "desc": "随后,constants.ts 中 WHITELIST_URLS 数组将注册端点路径硬编码为白名单条目,这是认证绕过在配置层面的静态根因。该字符串在全局中间件执行前缀匹配时被读取,使所有命中该路径的请求得以规避 JWT 和 SessionToken 验证,将路由层的入口与中间件层的免检逻辑在静态配置层面关联起来。", "file": "packages/server/src/utils/constants.ts", "line": 34}, {"code": " const isWhitelisted = whitelistURLs.some((url) => req.path.startsWith(url))\n if (isWhitelisted) {\n next()", "desc": "在请求到达认证中间件阶段,index.ts 对每条入站请求执行 whitelistURLs.some 前缀匹配:注册路径命中白名单时布尔值为真,随即调用 next() 跳过后续全部 JWT 与 APIKey 验证分支,将请求直接转发至路由处理器。这是从白名单配置层流向控制器层的运行时过渡节点,认证绕过在此处实际生效。", "file": "packages/server/src/index.ts", "line": "196-198"}, {"code": " public async register(req: Request, res: Response, next: NextFunction) {\n try {\n const accountService = new AccountService()\n const data = await accountService.register(req.body)\n return res.status(StatusCodes.CREATED).json(data)\n } catch (error) {\n next(error)\n }\n }", "desc": "随后,AccountController.register 从中间件层接收已放行的请求,实例化 AccountService 并将 req.body 未经任何结构或权限校验地直接传入 accountService.register(),以 HTTP 201 返回服务层结果。控制器层未插入补偿性访问控制,完成了从中间件层流向服务层的数据透传。", "file": "packages/server/src/enterprise/controllers/account.controller.ts", "line": "8-16"}, {"code": " public async register(data: AccountDTO) {\n return await this.saveRegisterAccount(data)\n }", "desc": "在调用栈下游,AccountService.register 公开方法接收控制器传入的 AccountDTO,以单条 await this.saveRegisterAccount(data) 无条件委托至私有实现,自身不附加任何业务前置条件。这一薄层包装是从控制器层流向私有持久化方法之间的无阻尼节点,来自未认证请求的数据在此不受任何干预地进入事务性写入流程。", "file": "packages/server/src/enterprise/services/account.service.ts", "line": "268-270"}, {"code": " private async saveRegisterAccount(data: AccountDTO) {\n const queryRunner = this.dataSource.createQueryRunner()\n await queryRunner.connect()\n const platform = this.identityManager.getPlatformType()\n const ownerRole = await this.roleService.readGeneralRoleByName(GeneralRole.OWNER, queryRunner)\n\n try {", "desc": "随后,saveRegisterAccount 私有方法建立数据库 QueryRunner 并连接,调用 identityManager.getPlatformType() 获取运行平台,再预查 OWNER 角色对象。这一初始化阶段既未统计已有账户数,也未判断系统是否已完成初始化,便将控制流导入平台分支逻辑;此处是从 AccountService.register 流向 switch 分支之间缺失关键前置校验的核心位置。", "file": "packages/server/src/enterprise/services/account.service.ts", "line": "231-237"}, {"code": " switch (platform) {\n case Platform.OPEN_SOURCE:\n data.organization.name = OrganizationName.DEFAULT_ORGANIZATION\n data.organizationUser.role = await this.roleService.readGeneralRoleByName(GeneralRole.OWNER, queryRunner)\n data.workspace.name = WorkspaceName.DEFAULT_WORKSPACE\n data.workspaceUser.role = data.organizationUser.role\n data.user.status = UserStatus.ACTIVE\n data.user = await this.userService.createNewUser(data.user, queryRunner)", "desc": "调用链终点的 switch(platform) 分支在 Platform.OPEN_SOURCE 路径下无条件将 organizationUser.role 与 workspaceUser.role 赋予 OWNER,并将 user.status 置为 ACTIVE 后调用 createNewUser 执行最终持久化。这是从 saveRegisterAccount 流向数据库写入操作的终点步骤,与调用链起点的免认证路由共同构成端到端的完整利用路径。", "file": "packages/server/src/enterprise/services/account.service.ts", "line": "112-119"}], "verify": 1, "vuln_category_l1": "业务逻辑", "vuln_category_l2": "BL-AUTHZ-MISSING(授权缺失)", "vuln_ids": ["GHSA-V5W9-PRXF-W882"], "vuln_title": "Flowise /api/v1/account/register 注册端点未授权访问绕过认证 (GHSA-v5w9-prxf-w882) - account.service.ts"} +{"commit": "7c803f4e0bd9e7a794a4ef2a2b45e58adff61a50", "critical_operation": {"code": " data.organizationUser.role = await this.roleService.readGeneralRoleByName(GeneralRole.OWNER, queryRunner)\n data.workspace.name = WorkspaceName.DEFAULT_WORKSPACE\n data.workspaceUser.role = data.organizationUser.role\n data.user.status = UserStatus.ACTIVE\n data.user = await this.userService.createNewUser(data.user, queryRunner)", "desc": "该代码块在注册服务终态将 organizationUser.role 赋值为 GeneralRole.OWNER、同步 workspaceUser.role、将 data.user.status 置为 ACTIVE,最终通过 createNewUser() 落库。在漏洞链路中,这是权限提升的关键落点:调用链到达此处之前从未校验系统内是否已存在 OWNER 账户,任何满足前置分支条件的请求均可在此创建拥有最高权限的新账户。", "file": "packages/server/src/enterprise/services/account.service.ts", "line": "199-203"}, "entry_id": "entry-00064", "entry_point": {"code": "router.use('/account', accountRouter)", "desc": "router.use('/account', accountRouter) 将账户子路由树挂载至全局 Express 路由器,使所有 /api/v1/account/* 路径的请求分发给 accountRouter 处理。在漏洞链路中,此挂载行为是 HTTP 层的对外暴露入口,未经认证的注册请求由此进入应用;后续是否触发 JWT 校验,取决于请求路径能否命中 WHITELIST_URLS,该节点是权限绕过链路的起点。", "file": "packages/server/src/routes/index.ts", "line": 134}, "origin": "GitHub Advisory Database (reviewed)", "project": "Flowise (FlowiseAI/Flowise)", "repo_url": "https://github.com/FlowiseAI/Flowise", "report_id": "GHSA-V5W9-PRXF-W882", "source_link": "https://github.com/advisories/GHSA-v5w9-prxf-w882", "trace": [{"code": "router.use('/account', accountRouter)", "desc": "router.use('/account', accountRouter) 是整条请求调用链的第一段,描述从外部 HTTP 客户端到 accountRouter 的路由桥接关系,/register 子路径因此对外可达。随后,accountRouter 内的路由处理器将请求分发至 AccountController,在此之前须经过全局认证中间件;该挂载行为是漏洞链路的对外暴露根节点,与 WHITELIST_URLS 的豁免配置共同决定注册请求能否绕过认证。", "file": "packages/server/src/routes/index.ts", "line": 134}, {"code": "export const WHITELIST_URLS = [", "desc": "WHITELIST_URLS 是声明在 constants.ts 中的全局免认证路径数组,其中列举的 URL 前缀在全局中间件中会被直接 next() 跳过所有 JWT 与 APIKey 验证。在漏洞链路中,该常量定义是白名单豁免机制的静态源头;注册端点被纳入此数组,使其永久免于身份认证检查,是认证绕过得以成立的配置层根因。", "file": "packages/server/src/utils/constants.ts", "line": 6}, {"code": " const whitelistURLs = WHITELIST_URLS", "desc": "const whitelistURLs = WHITELIST_URLS 在全局认证中间件函数体内将静态白名单常量载入运行时局部变量,随后中间件以此变量对每条入站请求路径做前缀匹配以决定是否跳过身份校验。在调用栈中,这是从静态配置层到运行时鉴权逻辑的传递节点;在漏洞链路中,whitelistURLs 的内容直接决定 /register 路径能否绕过 JWT 检查,是认证豁免机制的运行时触发点。", "file": "packages/server/src/index.ts", "line": 184}, {"code": "const accountController = new AccountController()", "desc": "const accountController = new AccountController() 在路由模块加载时实例化账户控制器,使 /register 路由能够将请求分发至 AccountController.register 方法。在调用栈中,该行是从路由层(accountRouter)流向控制层(AccountController)的衔接节点,是请求在中间件白名单豁免之后进入业务逻辑前的桥梁;在漏洞链路中,AccountController 的可用性是后续调用 AccountService 注册逻辑的前提。", "file": "packages/server/src/enterprise/routes/account.route.ts", "line": 7}, {"code": " case Platform.ENTERPRISE: {\n if (data.user.tempToken) {", "desc": "Platform.ENTERPRISE 分支入口处包含 data.user.tempToken 校验,此段代码是 AccountService 注册调用链从平台类型判断流向 Enterprise 专属账户创建路径的转折节点。在调用栈中游,该分支条件决定后续是否执行 OWNER 角色赋权逻辑;在漏洞链路中,此处仅验证 tempToken 存在性而未断言系统内是否已有 OWNER,随后的赋权操作因此可在无全局状态约束的情况下抵达 critical_operation。", "file": "packages/server/src/enterprise/services/account.service.ts", "line": "168-169"}, {"code": " data.user = await this.userService.createNewUser(data.user, queryRunner)", "desc": "createNewUser() 在 queryRunner 事务上下文中将 data.user 对象持久化写入数据库,完成新账户落库,并将返回的 User 实体更新回 data.user。在调用栈下游,该调用是从 AccountService 流向 UserService 的终端写入段;在漏洞链路中,抵达此处时 data.user.status 已为 ACTIVE 且角色已为 OWNER,该调用标志着未授权账户在数据层面的最终生成。", "file": "packages/server/src/enterprise/services/account.service.ts", "line": 203}, {"code": " data.organizationUser.role = await this.roleService.readGeneralRoleByName(GeneralRole.OWNER, queryRunner)\n data.workspace.name = WorkspaceName.DEFAULT_WORKSPACE\n data.workspaceUser.role = data.organizationUser.role\n data.user.status = UserStatus.ACTIVE\n data.user = await this.userService.createNewUser(data.user, queryRunner)", "desc": "在调用栈终端,该代码块汇聚五项赋权与持久化操作:解析 OWNER 角色、设定默认工作区名称、同步角色至 workspaceUser、激活用户状态,最终调用 createNewUser() 落库。与前序 trace 步骤相衔接:请求经路由挂载→白名单豁免→控制器初始化→平台分支判断逐层到达此处,这是漏洞链路在代码层的最终执行点,与 critical_operation 所描述的破坏性赋权操作完全对应。", "file": "packages/server/src/enterprise/services/account.service.ts", "line": "199-203"}], "verify": 1, "vuln_category_l1": "业务逻辑", "vuln_category_l2": "BL-AUTHZ-MISSING(授权缺失)", "vuln_ids": ["GHSA-V5W9-PRXF-W882"], "vuln_title": "Flowise /api/v1/account/register 注册端点未授权访问绕过认证 (GHSA-v5w9-prxf-w882) - account.service.ts"} +{"commit": "1ae1638ed972bcc913611ae9268a972d0ae127ec", "critical_operation": {"code": "const hash = bcrypt.hashSync(newUserData.password, salt)", "desc": "The bcrypt.hashSync call irreversibly replaces the stored credential with the attacker-supplied password. As the critical operation, this is where unauthorized account takeover is completed: because no current-password comparison preceded this call, any authenticated user who submitted a replacement value reaches this point and permanently changes the password without proving prior ownership.", "file": "packages/server/src/enterprise/services/user.service.ts", "line": 164}, "entry_id": "entry-00065", "entry_point": {"code": "id='newPassword'", "desc": "The newPassword input field in the Flowise account settings view accepts the user-supplied replacement password. This is the vulnerability entry point: no current-password input exists on this form, so the UI omits any ownership-proof step before sending the new value downstream to the update API.", "file": "packages/ui/src/views/account/index.jsx", "line": 741}, "origin": "GitHub Advisory Database (reviewed)", "project": "FlowiseAI/Flowise", "repo_url": "https://github.com/FlowiseAI/Flowise", "report_id": "GHSA-FJH6-8679-9PCH", "source_link": "https://github.com/advisories/GHSA-fjh6-8679-9pch", "trace": [{"code": " id='newPassword'", "desc": "The newPassword React input element marks the start of the taint chain: user-supplied plaintext password originates here and enters component state. The form includes no current-password input alongside it. This trace step anchors the vulnerability path at the UI-layer origin, confirming that untrusted credential data enters the system before any ownership verification can occur.", "file": "packages/ui/src/views/account/index.jsx", "line": 741}, {"code": "const savePassword = async () => {", "desc": "The savePassword async function initiates the password-change request from the account view. When called, it captures newPassword and confirmPassword from component state and validates only that they match. The function never reads or requires a current-password field, making it a direct pass-through that forwards the new credential to the API without any prior-ownership check.", "file": "packages/ui/src/views/account/index.jsx", "line": 241}, {"code": "if (newPassword !== confirmPassword) {", "desc": "This conditional checks that the new password and confirm-password inputs match before proceeding. It is the sole front-end validation: it compares only the two new-password copies and never checks the new value against any stored current password. Passing this check allows execution to proceed to payload construction without the user proving knowledge of the existing credential.", "file": "packages/ui/src/views/account/index.jsx", "line": 244}, {"code": "const obj = {", "desc": "The obj literal assembles the request body for the update endpoint, bundling account fields including the new password into a plain object. The absence of any currentPassword property is critical: the payload is constructed with only identity and new-credential data, so the old password is never transmitted and server-side prior-credential verification becomes impossible.", "file": "packages/ui/src/views/account/index.jsx", "line": 268}, {"code": "const saveProfileResp = await userApi.updateUser(obj)", "desc": "The call to userApi.updateUser dispatches the assembled payload to the backend over HTTP, transitioning the vulnerability from the UI layer to the network layer. The new-password value leaves the browser without any accompanying current-password token, and the server receives a request it will process as legitimate. The missing credential check must exist server-side or the account is exposed.", "file": "packages/ui/src/views/account/index.jsx", "line": 272}, {"code": "const updateUser = (body) => client.put(`/user`, body)", "desc": "The updateUser API helper issues an HTTP PUT request to the /user endpoint with the caller-supplied body verbatim. As the client-side network bridge, it forwards the payload without inspection. Because the body contains no current-password field, the server receives a request that lacks the credential needed to prove ownership, completing the front-end side of the unverified change.", "file": "packages/ui/src/api/user.js", "line": 5}, {"code": "public async update(req: Request, res: Response, next: NextFunction) {", "desc": "The update controller method receives the incoming PUT request as the server-side entry point for account updates. The method extracts the target user id and performs a narrow identity check; this is the first server-side opportunity to validate the current password, yet the method signature and body perform no such validation, allowing the request to proceed to the service layer unchallenged.", "file": "packages/server/src/enterprise/controllers/user.controller.ts", "line": 52}, {"code": "if (currentUser.id !== id) {", "desc": "This conditional compares the session user id against the request id, blocking cross-account modifications. It enforces account isolation but not credential ownership: the caller proves they own the target record, not that they know its current password. As the final guard before the service call, this is the controller's last missed opportunity to enforce prior-credential verification.", "file": "packages/server/src/enterprise/controllers/user.controller.ts", "line": 60}, {"code": "const user = await userService.updateUser(req.body)", "desc": "The controller passes the full request body to userService.updateUser without pre-processing. This is the handoff from the HTTP layer to business logic: req.body arrives intact with the new password and no current-password field. The service method receiving this call is the last opportunity to enforce credential verification before the password is overwritten.", "file": "packages/server/src/enterprise/controllers/user.controller.ts", "line": 63}, {"code": "public async updateUser(newUserData: Partial & { password?: string }) {", "desc": "The updateUser service method receives the request body and owns the credential-update logic. Its signature accepts an optional password field but no currentPassword parameter, making it structurally incapable of verifying ownership of the existing credential. This is the last server-side location before the hash operation; the vulnerability's missing check should be placed here.", "file": "packages/server/src/enterprise/services/user.service.ts", "line": 137}, {"code": "if (newUserData.password) {", "desc": "This conditional verifies only that a new password field exists in the request body, then enters the credential-replacement block. It never compares the incoming value against the stored credential or requires a current-password proof. As the immediate gate before the critical hash operation, this existence-only check is the final missed opportunity to enforce ownership verification.", "file": "packages/server/src/enterprise/services/user.service.ts", "line": 161}, {"code": " const hash = bcrypt.hashSync(newUserData.password, salt)", "desc": "The bcrypt.hashSync call hashes the caller-supplied password and overwrites the stored credential. No current-password verification has occurred anywhere in the call stack at this point: the hash proceeds for any authenticated user regardless of existing-credential knowledge. This trace step is co-located with the critical-operation anchor and marks the end of the taint path.", "file": "packages/server/src/enterprise/services/user.service.ts", "line": 164}], "verify": 1, "vuln_category_l1": "业务逻辑", "vuln_category_l2": "BL-WORKFLOW-VIOLATION(业务流程/状态机违规)", "vuln_ids": ["GHSA-FJH6-8679-9PCH"], "vuln_title": "Flowise 修改密码接口缺失旧密码验证导致账户接管(CWE-620 / GHSA-fjh6-8679-9pch)"} +{"commit": "b7d1831f9d3560ed4fb45134861eef3f4544eff3", "critical_operation": {"code": " return DEFAULT_FORMATTER_MAPPING[self.template_format](self.template, **kwargs)", "desc": "DEFAULT_FORMATTER_MAPPING 以 self.template_format 为键查找对应格式化函数,随即将 self.template 与 kwargs 传入执行。此处是将存储模板交付底层引擎的核心调度点:f-string、Mustache、Jinja2 三条路径均在此将未经字段名净化的模板字符串转交引擎,模板中嵌入的属性遍历表达式在此被实际触发求值,是注入语义生效的直接触发位置。", "file": "libs/core/langchain_core/prompts/prompt.py", "line": 198}, "entry_id": "entry-00066", "entry_point": {"code": " @classmethod", "desc": "@classmethod 修饰符将 from_template 声明为 PromptTemplate 的工厂类方法,外部调用者可向其传入任意字符串作为模板内容。该方法在实例化阶段不对模板字段名执行标识符合法性验证,使携带属性遍历路径的模板字符串可无阻通过构造流程,是漏洞利用链中外部可控输入进入系统的初始入口。", "file": "libs/core/langchain_core/prompts/prompt.py", "line": 251}, "origin": "GitHub Advisory Database (reviewed)", "project": "langchain-core", "repo_url": "https://github.com/langchain-ai/langchain", "report_id": "GHSA-6QV9-48XG-FC7F", "source_link": "https://github.com/advisories/GHSA-6qv9-48xg-fc7f", "trace": [{"code": " @classmethod", "desc": "@classmethod 修饰符使 from_template 成为 PromptTemplate 的类级工厂方法,是整条漏洞调用链的起始节点。从 caller 流向 callee 的视角看,外部调用方将可控模板字符串传入此处,随后由 from_template 内部顺序驱动模板变量解析与实例初始化,外部输入自此向调用栈内层流动,为后续各无校验步骤埋下前提。", "file": "libs/core/langchain_core/prompts/prompt.py", "line": 251}, {"code": " input_variables = get_template_variables(template, template_format)", "desc": "from_template 内部随后调用 get_template_variables(template, template_format),将模板字符串与格式类型一并传入并将返回的变量名列表赋给 input_variables。此处本应是构造期对模板字段名的拦截点,但 f-string 分支依赖 Formatter().parse() 枚举字段名而不验证标识符合法性,含属性遍历路径的字段名被原样保留,为调用栈下游的 format() 阶段埋下隐患。", "file": "libs/core/langchain_core/prompts/prompt.py", "line": 289}, {"code": " elif template_format == \"f-string\":", "desc": "get_template_variables 内部的 elif template_format == \"f-string\" 分支将字段提取逻辑路由至 f-string 专用处理路径。随后该路径调用 Python 原生 Formatter().parse() 逐一提取字段名,不要求字段名为简单标识符;含点分隔符的多级属性路径因此被视为合法变量,在调用栈下游的 format() 渲染阶段可被格式化机制按链式 getattr 依次求值。", "file": "libs/core/langchain_core/prompts/string.py", "line": 266}, {"code": " def format(self, **kwargs: Any) -> str:", "desc": "PromptTemplate.format 方法接收运行时 **kwargs,是从构造阶段过渡至渲染执行阶段的关键边界。调用者将变量键值对传入此处,format 内部随后将 self.template 与 kwargs 一并交付 DEFAULT_FORMATTER_MAPPING 中对应的格式化引擎,驱动存储模板的实际展开,使构造阶段未被拦截的属性遍历字段名在此步正式进入求值路径。", "file": "libs/core/langchain_core/prompts/prompt.py", "line": 188}, {"code": " return super().vformat(format_string, args, kwargs)", "desc": "自定义格式化器内部的 super().vformat(format_string, args, kwargs) 将格式字符串的解析委托给 Python 标准库 string.Formatter.vformat。标准 vformat 对形如 {a.b.c} 的字段名按点号逐级执行 getattr,不对属性名做合法性检查;在从 format() 经 DEFAULT_FORMATTER_MAPPING 流向此处的调用路径上,含属性遍历语法的字段名在这一步被最终求值,是注入生效的执行终点。", "file": "libs/core/langchain_core/utils/formatting.py", "line": 33}, {"code": " return DEFAULT_FORMATTER_MAPPING[self.template_format](self.template, **kwargs)", "desc": "DEFAULT_FORMATTER_MAPPING[self.template_format](self.template, **kwargs) 在 format() 末尾执行,将 self.template 与 kwargs 传入对应引擎,是 format() 向底层格式化器传递控制权的调度节点。底层引擎接收未净化的模板字符串后继续向调用栈深处传递,随后在 vformat 层对属性遍历字段名执行链式 getattr,与 critical_operation 指向同一语句,共同标记注入路径的终点。", "file": "libs/core/langchain_core/prompts/prompt.py", "line": 198}], "verify": 1, "vuln_category_l1": "模板注入", "vuln_category_l2": "服务端模板注入 (SSTI)", "vuln_ids": ["CVE-2025-65106", "GHSA-6QV9-48XG-FC7F"], "vuln_title": "LangChain Template Injection via Attribute Access in Prompt Templates (GHSA-6qv9-48xg-fc7f) - prompt.py"} +{"commit": "b7d1831f9d3560ed4fb45134861eef3f4544eff3", "critical_operation": {"code": " resolved_scope = getattr(resolved_scope, child)", "desc": "getattr(resolved_scope, child) 在 dict 下标访问抛出 TypeError 或 AttributeError 后作为兜底手段被无条件执行,child 直接取自模板点分路径的当前片段,未经类型检查或名称白名单过滤。这是整条漏洞链路的最终汇聚点:当模板路径含 __class__ 等 Python 内置属性名时,getattr 直接对渲染上下文中的任意对象执行属性访问,致使对象内部状态通过渲染结果外泄。", "file": "libs/core/langchain_core/utils/mustache.py", "line": 382}, "entry_id": "entry-00067", "entry_point": {"code": "def mustache_formatter(template: str, /, **kwargs: Any) -> str:", "desc": "mustache_formatter 是 Mustache 模板渲染的公开接口函数,以位置参数接收外部传入的 template 字符串,以 **kwargs 接收任意键值对作为渲染上下文。函数体不对模板内容作任何语法或语义校验,直接将输入下发至内部 mustache 引擎。在漏洞链路中,此函数是不可信模板字符串的流入节点:若调用方将外部字符串作为模板传入,其中携带的点分属性路径将从此处无阻进入渲染流程。", "file": "libs/core/langchain_core/prompts/string.py", "line": 111}, "origin": "GitHub Advisory Database (reviewed)", "project": "langchain-core", "repo_url": "https://github.com/langchain-ai/langchain", "report_id": "GHSA-6QV9-48XG-FC7F", "source_link": "https://github.com/advisories/GHSA-6qv9-48xg-fc7f", "trace": [{"code": "def mustache_formatter(template: str, /, **kwargs: Any) -> str:", "desc": "mustache_formatter 函数签名定义了 Mustache 渲染的调用契约:template 以纯位置参数形式接收模板字符串,**kwargs 接收键值渲染数据,二者均未经内容格式校验。作为调用链的起始节点,此函数将来自上层调用者的外部输入无条件汇聚并向下传递;若模板字符串携带含点分属性路径的恶意内容,漏洞利用路径即从此节点启动,随后进入渲染下游。", "file": "libs/core/langchain_core/prompts/string.py", "line": 111}, {"code": " return mustache.render(template, kwargs)", "desc": "mustache.render(template, kwargs) 将未经清洗的模板字符串和数据字典整体交给 mustache 引擎的核心渲染函数,标志调用栈从 prompts/string.py 跨越至 utils/mustache.py。模板字符串的控制权在此完全移交底层引擎,随后的 tokenize 与变量解析将决定点分路径如何被处理;这是漏洞路径从接口层向核心执行层传递的关键跳转,也是调用链中最后一个可插入验证逻辑的上层节点。", "file": "libs/core/langchain_core/prompts/string.py", "line": 121}, {"code": " tokens = tokenize(template, def_ldel, def_rdel)", "desc": "tokenize(template, def_ldel, def_rdel) 将原始模板字符串解析为 (tag_type, key) 令牌流,其中变量标签的 key 保留完整点分路径原文(如 __class__.__name__),不作标识符合法性校验。随后这些令牌被 render() 主循环逐一消费;每个 variable 类型令牌的点分 key 将被原样传入 _get_key(),使恶意属性路径在此环节得以完整保存,为下游属性遍历提供未经过滤的输入。", "file": "libs/core/langchain_core/utils/mustache.py", "line": 482}, {"code": " thing = _get_key(", "desc": "_get_key(key, scopes, ...) 调用点将当前 variable 令牌的完整点分路径 key 连同作用域栈 scopes 传入键解析函数,是 render() 主循环向属性遍历逻辑的分发节点。在调用栈下游,_get_key 将对 key 逐段解析;若路径含攻击者注入的属性名,执行流自此进入实际 Python 对象属性访问路径,是漏洞从渲染主循环向底层 getattr 逻辑扩散的关键跳转点。", "file": "libs/core/langchain_core/utils/mustache.py", "line": 513}, {"code": " for child in key.split(\".\"):", "desc": "key.split(\".\") 将点分路径拆解为独立片段,for child in ... 循环驱动 _get_key() 对每个 child 依次推进当前作用域对象的解析。此循环是多级属性遍历成为可能的结构根源:路径 a.b.c 产生三次独立迭代,每次均执行下标访问或 getattr 尝试。在漏洞链路中,正是这一循环使 __class__.__name__ 等嵌套敏感路径能被逐级穿透,随后每次迭代的兜底 getattr 依次暴露对象内部状态。", "file": "libs/core/langchain_core/utils/mustache.py", "line": 371}, {"code": " try:", "desc": "内层 try 块为每个路径片段 child 建立双重降级策略:首先尝试 dict 下标访问(resolved_scope[child]),捕获 TypeError 或 AttributeError 后进入嵌套 try,再尝试 getattr(resolved_scope, child),仍失败则回退列表整数下标。这套无限制降级机制使字典之外的任意 Python 对象成为属性遍历的目标;在漏洞链路中,此 try 块是 getattr 被触发的直接前驱:渲染上下文含非字典对象时执行流必然进入 getattr 分支。", "file": "libs/core/langchain_core/utils/mustache.py", "line": 377}, {"code": " resolved_scope = getattr(resolved_scope, child)", "desc": "getattr(resolved_scope, child) 作为下标访问失败后的兜底被无条件执行,child 直接取自点分路径的当前片段,无任何合法性校验,访问结果被赋回 resolved_scope 供后续循环迭代继续向下遍历。在调用栈的最底层,此行实现对当前作用域对象的任意属性读取,是整条调用链的最终执行位置:含 __class__ 等敏感属性名的路径至此被 Python 运行时直接解析,内部对象状态随渲染结果暴露给调用方。", "file": "libs/core/langchain_core/utils/mustache.py", "line": 382}], "verify": 1, "vuln_category_l1": "模板注入", "vuln_category_l2": "服务端模板注入 (SSTI)", "vuln_ids": ["CVE-2025-65106", "GHSA-6QV9-48XG-FC7F"], "vuln_title": "LangChain Template Injection via Attribute Access in Prompt Templates (GHSA-6qv9-48xg-fc7f) - mustache.py"} +{"commit": "b7d1831f9d3560ed4fb45134861eef3f4544eff3", "critical_operation": {"code": " return SandboxedEnvironment().from_string(template).render(**kwargs)", "desc": "此处实例化 SandboxedEnvironment,将 template 编译为模板对象后立即以 kwargs 渲染并返回结果。该沙箱仅拦截危险 dunder 方法,不限制普通属性读取与方法调用,致使 {{ obj.attr }} 形式的表达式可访问注入对象的内部状态,是漏洞的实际触发点。", "file": "libs/core/langchain_core/prompts/string.py", "line": 71}, "entry_id": "entry-00068", "entry_point": {"code": "def jinja2_formatter(template: str, /, **kwargs: Any) -> str:", "desc": "jinja2_formatter 是 LangChain Jinja2 渲染路径的公开入口,以 positional-only 方式接收模板字符串,并通过 **kwargs 收集渲染上下文对象。外部可控的模板文本与上下文在此汇聚,是属性遍历型模板注入漏洞链路的起始位置。", "file": "libs/core/langchain_core/prompts/string.py", "line": 32}, "origin": "GitHub Advisory Database (reviewed)", "project": "langchain-core", "repo_url": "https://github.com/langchain-ai/langchain", "report_id": "GHSA-6QV9-48XG-FC7F", "source_link": "https://github.com/advisories/GHSA-6qv9-48xg-fc7f", "trace": [{"code": "def jinja2_formatter(template: str, /, **kwargs: Any) -> str:", "desc": "漏洞链路第一步:jinja2_formatter 被调用,template 字符串与 kwargs 上下文对象以函数参数的形式进入,外部输入此时尚未经历任何内容层面的验证,后续流程将依次经过库可用性检测,最终到达渲染阶段。", "file": "libs/core/langchain_core/prompts/string.py", "line": 32}, {"code": " if not _HAS_JINJA2:", "desc": "链路第二步:函数检查 _HAS_JINJA2 标志以确认 jinja2 库是否已安装;此分支仅作可用性守卫,不对 template 内容或 kwargs 对象施加任何安全过滤,检测通过后执行流直接向下进入存在属性遍历风险的渲染逻辑。", "file": "libs/core/langchain_core/prompts/string.py", "line": 54}, {"code": " return SandboxedEnvironment().from_string(template).render(**kwargs)", "desc": "链路末端:SandboxedEnvironment 被实例化并将 template 编译后以 kwargs 渲染,传入的对象直接暴露给模板引擎。沙箱仅屏蔽 dunder 方法,普通属性访问完全开放,{{ obj.attr }} 形式的表达式在此得以实际执行,对象内部状态可在此步骤被读取。", "file": "libs/core/langchain_core/prompts/string.py", "line": 71}], "verify": 1, "vuln_category_l1": "模板注入", "vuln_category_l2": "服务端模板注入 (SSTI)", "vuln_ids": ["CVE-2025-65106", "GHSA-6QV9-48XG-FC7F"], "vuln_title": "LangChain Template Injection via Attribute Access in Prompt Templates (GHSA-6qv9-48xg-fc7f) - string.py"} +{"commit": "908c141d972118286d682124ce53ced16a4140ea", "critical_operation": {"code": " messages = session.exec(stmt)", "desc": "session.exec(stmt) 将上游拼装好的 SELECT 语句提交至数据库并将结果赋给 messages,是整个查询链路的最终执行点。由于调用链从路由注册到此处均未声明 get_current_active_user 依赖,数据库在此时直接返回 MessageTable 中符合过滤条件的全部记录,无跨用户隔离,导致任意未认证请求均可借此读取所有用户的对话内容。", "file": "src/backend/base/langflow/api/v1/monitor.py", "line": 68}, "entry_id": "entry-00071", "entry_point": {"code": "@router.get(\"/messages\", response_model=list[MessageResponse])", "desc": "router.get(\"/messages\") 装饰器将 GET /messages 端点注册至路由层,但省略了 get_current_active_user 依赖声明,FastAPI 框架因此不会在请求进入处理函数前校验调用方身份。与同文件已正确配置认证的 DELETE /messages 路由相比,此处缺失的 dependencies 参数使整条消息查询链路对未经认证的客户端完全开放,是漏洞链路的初始暴露节点。", "file": "src/backend/base/langflow/api/v1/monitor.py", "line": 46}, "origin": "GitHub Advisory Database (reviewed)", "project": "langflow", "repo_url": "https://github.com/langflow-ai/langflow", "report_id": "GHSA-C5CP-VX83-JHQX", "source_link": "https://github.com/advisories/GHSA-c5cp-vx83-jhqx", "trace": [{"code": "@router.get(\"/messages\", response_model=list[MessageResponse])", "desc": "router.get(\"/messages\") 装饰器是调用链的第一跳,在 FastAPI 路由注册阶段确定该端点的依赖集合。由于此处未声明 get_current_active_user,框架的依赖注入机制不会为后续处理插入身份校验步骤,整条链路从起点便缺少认证屏障;随后请求将无阻碍地流入 get_messages 处理函数,进入漏洞链路的下一阶段。", "file": "src/backend/base/langflow/api/v1/monitor.py", "line": 46}, {"code": "async def get_messages(", "desc": "async def get_messages 是从 FastAPI 路由调度器(caller)流向消息查询业务逻辑(callee)的入口节点。函数签名中仅通过 Depends(get_session) 注入数据库会话,未包含任何认证依赖,控制流在此进入函数体时请求方身份始终未经核验;随后函数将依据请求参数构建查询语句,沿调用栈向下推进。", "file": "src/backend/base/langflow/api/v1/monitor.py", "line": 47}, {"code": " stmt = select(MessageTable)", "desc": "stmt = select(MessageTable) 在函数体内构造针对 MessageTable 的基础 SELECT 语句,并在调用栈下游逐步附加可选的 flow_id、session_id、sender 等过滤条件。由于上游路由层从未施加认证约束,此处生成的语句未作任何 owner 隔离;随后该语句将被直接传递给 session.exec 执行,缺乏访问控制的查询构建为最终的跨用户数据泄露做好铺垫。", "file": "src/backend/base/langflow/api/v1/monitor.py", "line": 56}, {"code": " messages = session.exec(stmt)", "desc": "session.exec(stmt) 是调用链的终点,将 trace 上游构建的 SELECT 语句提交至数据库并返回结果集。从路由注册到函数入口再到语句构建的整个上游链路中,始终未执行身份认证,此处数据库直接响应查询并返回跨用户消息记录,完成了无授权访问的最终动作,也是信息泄露危害落地的执行点。", "file": "src/backend/base/langflow/api/v1/monitor.py", "line": 68}], "verify": 1, "vuln_category_l1": "业务逻辑", "vuln_category_l2": "BL-AUTHZ-MISSING(授权缺失)", "vuln_ids": ["CVE-2026-21445", "GHSA-C5CP-VX83-JHQX"], "vuln_title": "Langflow monitor API 三端点缺少身份认证 (CVE-2026-21445) - monitor.py"} +{"commit": "908c141d972118286d682124ce53ced16a4140ea", "critical_operation": {"code": " transactions = get_transactions_by_flow_id(session, flow_id)", "desc": "get_transactions_by_flow_id(session, flow_id) 以外部传入的 flow_id 直接发起数据库查询,返回对应 flow 的全部事务记录,包含 inputs、outputs 及错误信息。由于上游路由未声明认证依赖,flow_id 完全由未经鉴权的请求方提供,查询无 user_id 关联约束,事务执行细节直接序列化返回,构成跨用户数据泄露的触发点。", "file": "src/backend/base/langflow/api/v1/monitor.py", "line": 175}, "entry_id": "entry-00072", "entry_point": {"code": "@router.get(\"/transactions\", response_model=list[TransactionReadResponse])", "desc": "路由装饰器 @router.get(\"/transactions\") 将 HTTP GET 端点注册到 FastAPI router,并声明响应类型为 TransactionReadResponse 列表。该装饰器仅注入 session 依赖而未声明 get_current_active_user 依赖,导致 FastAPI 不验证请求方的身份令牌,使未认证请求可直接落入 get_transactions 函数体。这是整条漏洞链路的入口——认证依赖的缺失是根本缺陷所在。", "file": "src/backend/base/langflow/api/v1/monitor.py", "line": 169}, "origin": "GitHub Advisory Database (reviewed)", "project": "langflow", "repo_url": "https://github.com/langflow-ai/langflow", "report_id": "GHSA-C5CP-VX83-JHQX", "source_link": "https://github.com/advisories/GHSA-c5cp-vx83-jhqx", "trace": [{"code": "@router.get(\"/transactions\", response_model=list[TransactionReadResponse])", "desc": "HTTP GET /transactions 端点的路由注册行,是外部请求进入系统的第一个着陆点。@router.get 装饰器在此处声明了响应类型但未注入 get_current_active_user 依赖,使鉴权屏障在请求到达之前即已缺席。随后未认证请求将直接流向 get_transactions 函数定义,开启完整的漏洞调用链。", "file": "src/backend/base/langflow/api/v1/monitor.py", "line": 169}, {"code": "async def get_transactions(", "desc": "随后控制流从路由装饰器流入 get_transactions 函数定义。该函数接收 flow_id(来自 Query 参数、类型为 UUID)与注入的 session,函数签名中不含任何认证用户对象,意味着 flow_id 完全来自请求方,未经任何 owner 绑定。下一步该函数体将以此 flow_id 直接调用数据库查询接口。", "file": "src/backend/base/langflow/api/v1/monitor.py", "line": 170}, {"code": " transactions = get_transactions_by_flow_id(session, flow_id)", "desc": "在调用栈下游,get_transactions_by_flow_id(session, flow_id) 以前一步传入的未认证 flow_id 执行数据库查询,缺乏 user_id 关联约束,任意 flow 的事务记录均可被读取并返回。这是漏洞调用链的终点——从无认证入口到无权限约束的数据访问完成闭环,查询结果直接序列化返回给请求方。", "file": "src/backend/base/langflow/api/v1/monitor.py", "line": 175}], "verify": 1, "vuln_category_l1": "业务逻辑", "vuln_category_l2": "BL-AUTHZ-MISSING(授权缺失)", "vuln_ids": ["CVE-2026-21445", "GHSA-C5CP-VX83-JHQX"], "vuln_title": "Langflow monitor API 三端点缺少身份认证 (CVE-2026-21445) - monitor.py"} +{"commit": "908c141d972118286d682124ce53ced16a4140ea", "critical_operation": {"code": " session.exec(\n delete(MessageTable)\n .where(col(MessageTable.session_id) == session_id)\n .execution_options(synchronize_session=\"fetch\")\n )", "desc": "session.exec 构造并执行针对 MessageTable 的等值批量删除语句,以传入的 session_id 作为 WHERE 条件过滤目标行,随后在同一事务中提交清除,fetch 模式确保 ORM 内存对象与数据库状态一致。此步骤是请求链路的终端写操作;由于上游路由装饰器缺失身份验证依赖,任何请求者均可在无令牌的情况下触发,造成对任意会话消息的不可逆清除。", "file": "src/backend/base/langflow/api/v1/monitor.py", "line": "157-161"}, "entry_id": "entry-00073", "entry_point": {"code": "@router.delete(\"/messages/session/{session_id}\", status_code=204)", "desc": "@router.delete 装饰器将 DELETE /messages/session/{session_id} 路由注册到 FastAPI router,标注响应状态码为 204,但未携带 dependencies=[Depends(get_current_active_user)] 参数。同文件 delete(\"/messages\") 端点已正确声明该依赖,此处缺失使 FastAPI 不插入身份验证环节,是漏洞链路面向外部请求的入口节点。", "file": "src/backend/base/langflow/api/v1/monitor.py", "line": 151}, "origin": "GitHub Advisory Database (reviewed)", "project": "langflow", "repo_url": "https://github.com/langflow-ai/langflow", "report_id": "GHSA-C5CP-VX83-JHQX", "source_link": "https://github.com/advisories/GHSA-c5cp-vx83-jhqx", "trace": [{"code": "@router.delete(\"/messages/session/{session_id}\", status_code=204)", "desc": "@router.delete 装饰器是调用栈的起始节点,将 /messages/session/{session_id} 路径绑定到处理函数,并向 FastAPI 声明 HTTP 方法与状态码。在调用链视角下,此处因未配置 get_current_active_user 依赖,导致整条链路自最外层起便处于无身份验证状态,是漏洞可触达路径的根源。", "file": "src/backend/base/langflow/api/v1/monitor.py", "line": 151}, {"code": "async def delete_messages_session(", "desc": "async def delete_messages_session 是与路由装饰器配套的异步处理函数,随后由 FastAPI 框架在收到匹配请求时调度执行。函数参数列表仅声明了 session_id 路径参数与数据库 session,在调用栈中扮演从路由分发到数据库操作的中间衔接角色,未引入任何权限校验逻辑,继承了上层装饰器缺失的身份验证约束。", "file": "src/backend/base/langflow/api/v1/monitor.py", "line": 152}, {"code": " session_id: str,", "desc": "session_id: str 是 delete_messages_session 的 URL 路径参数,FastAPI 自动将请求路径中的对应段解析为字符串注入此处。在调用栈下游,该变量将直接作为 DELETE 语句的 WHERE 过滤条件,决定哪批 MessageTable 记录被删除;由于路径入口无访问控制,请求者可任意指定该值,使此参数成为跨会话数据删除的操控点。", "file": "src/backend/base/langflow/api/v1/monitor.py", "line": 153}, {"code": " session.exec(\n delete(MessageTable)\n .where(col(MessageTable.session_id) == session_id)\n .execution_options(synchronize_session=\"fetch\")\n )", "desc": "session.exec 在调用栈最底层发送批量删除指令,以 session_id 过滤 MessageTable 并提交事务,fetch 同步选项保证 ORM 层感知数据变更。这是整条请求链路的终端写操作,直接落地漏洞危害;由于路由装饰器与函数签名均未实施身份验证,此处在无任何授权核验的情况下执行,完成对目标会话消息的不可逆批量删除。", "file": "src/backend/base/langflow/api/v1/monitor.py", "line": "157-161"}], "verify": 1, "vuln_category_l1": "业务逻辑", "vuln_category_l2": "BL-AUTHZ-MISSING(授权缺失)", "vuln_ids": ["CVE-2026-21445", "GHSA-C5CP-VX83-JHQX"], "vuln_title": "Langflow monitor API 三端点缺少身份认证 (CVE-2026-21445) - monitor.py"} +{"commit": "7325791599409de52534429897481918717a9e85", "critical_operation": {"code": "func (s *Server) PullHandler(c *gin.Context) {", "desc": "PullHandler 是 /api/pull 端点的 gin 处理函数,接收 gin.Context 后直接进入 ShouldBindJSON 解析请求体、校验模型名称、触发模型拉取流程,函数体内不含任何认证或授权前置检查;这一设计使整条调用链在此节点完全绕过权限验证,是漏洞链路中缺失认证守卫的关键执行位置。", "file": "server/routes.go", "line": 848}, "entry_id": "entry-00074", "entry_point": {"code": "\tr.POST(\"/api/pull\", s.PullHandler)", "desc": "GenerateRoutes 函数在路由注册段调用 r.POST 将 /api/pull 端点绑定至 PullHandler,此时中间件链仅含 CORS 策略处理器和 allowedHostsMiddleware(基于 Host 头的 IP 地址校验),未挂载任何身份验证节点;该注册行是漏洞链路中未经认证的外部 HTTP 请求被 gin 框架合法接纳并派发的暴露起点。", "file": "server/routes.go", "line": 1503}, "origin": "GitHub Advisory Database (reviewed)", "project": "ollama", "repo_url": "https://github.com/ollama/ollama", "report_id": "GHSA-F6MR-38G8-39RG", "source_link": "https://github.com/advisories/GHSA-f6mr-38g8-39rg", "trace": [{"code": "\tr.POST(\"/api/pull\", s.PullHandler)", "desc": "GenerateRoutes 在初始化阶段构建路由表,通过 r.POST 将 /api/pull 路径注册到 gin 引擎;随后每当外部发起 POST /api/pull 请求,gin 即按此路由表将 HTTP 请求上下文直接派发至 PullHandler,中间不经过任何认证中间件拦截——这是调用链的起始跳转节点,决定了请求在无身份验证的条件下能够到达后续处理逻辑。", "file": "server/routes.go", "line": 1503}, {"code": "func (s *Server) PullHandler(c *gin.Context) {", "desc": "从 trace[0] 的路由派发层流向此函数后,PullHandler 以 gin.Context 为入口,依次执行 ShouldBindJSON 解析 JSON 请求体、校验模型名称合法性,随后直接触发模型拉取操作;在整个从路由派发到业务执行的链路中,始终不存在认证前置守卫,使得任意未认证请求均可在此完成完整的模型拉取逻辑。", "file": "server/routes.go", "line": 848}], "verify": 1, "vuln_category_l1": "业务逻辑", "vuln_category_l2": "BL-AUTHZ-MISSING(授权缺失)", "vuln_ids": ["CVE-2025-63389", "GHSA-F6MR-38G8-39RG"], "vuln_title": "Ollama 多个 API 端点缺少服务端认证导致未授权模型管理操作 (CVE-2025-63389) - routes.go"} +{"commit": "7325791599409de52534429897481918717a9e85", "critical_operation": {"code": "\tif err := m.Remove(); err != nil {", "desc": "m.Remove() 调用底层存储接口,将指定模型的清单记录从本地文件系统删除。该调用位于整条处理链路的末端,其前序路径中没有任何一处执行了调用方身份验证或权限检查,因此这一删除操作可由任意未经授权的外部请求直接驱动,在漏洞链路中充当不可逆破坏的终止节点。", "file": "server/routes.go", "line": 1015}, "entry_id": "entry-00075", "entry_point": {"code": "\tr.DELETE(\"/api/delete\", s.DeleteHandler)", "desc": "r.DELETE 在全局路由器 r 上将路径 /api/delete 与 s.DeleteHandler 绑定,整个注册过程未附加任何认证或授权中间件。在漏洞链路中,这一语句是权限缺失的暴露起点:路由层不验证调用方身份,将所有命中该路径的 DELETE 请求统一派发至删除处理器。", "file": "server/routes.go", "line": 1508}, "origin": "GitHub Advisory Database (reviewed)", "project": "ollama", "repo_url": "https://github.com/ollama/ollama", "report_id": "GHSA-F6MR-38G8-39RG", "source_link": "https://github.com/advisories/GHSA-f6mr-38g8-39rg", "trace": [{"code": "\tr.DELETE(\"/api/delete\", s.DeleteHandler)", "desc": "路由注册语句将 /api/delete 端点与 s.DeleteHandler 绑定,是漏洞调用链的起始节点;从 caller 视角看,路由器 r 在此处承担请求分发职责,将后续所有命中该路径的 DELETE 请求无鉴权地交付给 DeleteHandler,为整条未授权调用链路提供了第一跳入口。", "file": "server/routes.go", "line": 1508}, {"code": "func (s *Server) DeleteHandler(c *gin.Context) {\n\tvar r api.DeleteRequest\n\tif err := c.ShouldBindJSON(&r); errors.Is(err, io.EOF) {\n\t\tc.AbortWithStatusJSON(http.StatusBadRequest, gin.H{\"error\": \"missing request body\"})\n\t\treturn\n\t} else if err != nil {\n\t\tc.AbortWithStatusJSON(http.StatusBadRequest, gin.H{\"error\": err.Error()})\n\t\treturn\n\t}", "desc": "DeleteHandler 是调用链从路由分发流入实际处理逻辑的 callee 入口,函数体通过 c.ShouldBindJSON 完成请求体格式合法性校验,确认 model 字段存在后即向下继续执行,全程未对调用方身份进行鉴权。随后控制流传递至模型查找与删除逻辑,这一函数体构成了从 HTTP 路由层到存储操作层的过渡节点,是权限缺失穿透至破坏性操作的关键中间跳。", "file": "server/routes.go", "line": "982-990"}, {"code": "\tif err := m.Remove(); err != nil {\n\t\tc.JSON(http.StatusInternalServerError, gin.H{\"error\": err.Error()})\n\t\treturn\n\t}\n\n\tif err := m.RemoveLayers(); err != nil {\n\t\tc.JSON(http.StatusInternalServerError, gin.H{\"error\": err.Error()})\n\t\treturn\n\t}", "desc": "在调用栈下游,m.Remove() 删除模型清单记录,随后 m.RemoveLayers() 清除该模型关联的所有层数据 blob,两步操作共同完成对本地模型的完整移除。由于 DeleteHandler 未对请求来源执行身份验证,该代码块在漏洞链路中充当不可逆的终端破坏节点,任意未授权请求均可驱动其完整执行。", "file": "server/routes.go", "line": "1015-1023"}], "verify": 1, "vuln_category_l1": "业务逻辑", "vuln_category_l2": "BL-AUTHZ-MISSING(授权缺失)", "vuln_ids": ["CVE-2025-63389", "GHSA-F6MR-38G8-39RG"], "vuln_title": "Ollama 多个 API 端点缺少服务端认证导致未授权模型管理操作 (CVE-2025-63389) - routes.go"} +{"commit": "7325791599409de52534429897481918717a9e85", "critical_operation": {"code": "\tc.JSON(http.StatusOK, api.ListResponse{Models: models})", "desc": "ListHandler 执行至此行时,将已查询到的本地模型列表封装为 api.ListResponse,以 HTTP 200 状态码写入响应体,模型名称、大小与量化级别等元数据随之离开服务器。c.JSON 是漏洞链路中敏感数据外泄的最终操作,标志着信息在网络层面的完成输出。", "file": "server/routes.go", "line": 1266}, "entry_id": "entry-00076", "entry_point": {"code": "\tr.GET(\"/api/tags\", s.ListHandler)", "desc": "GenerateRoutes 函数在此处将 GET /api/tags 路由绑定至 ListHandler,所属 gin 路由器的中间件链仅含 CORS 与 allowedHostsMiddleware,完全缺少认证检查。该绑定使 /api/tags 端点在无任何鉴权保护的状态下对外暴露,是整条未认证模型枚举漏洞链路的外部入口。", "file": "server/routes.go", "line": 1506}, "origin": "GitHub Advisory Database (reviewed)", "project": "ollama", "repo_url": "https://github.com/ollama/ollama", "report_id": "GHSA-F6MR-38G8-39RG", "source_link": "https://github.com/advisories/GHSA-f6mr-38g8-39rg", "trace": [{"code": "\tr.GET(\"/api/tags\", s.ListHandler)", "desc": "GenerateRoutes 在此行将 /api/tags 注册为 gin 路由器上的 GET 端点,并指定 ListHandler 为处理函数,构成从外部 HTTP 请求到 callee 的调用入口。路由器中间件链缺乏认证拦截,任意请求均可沿此路径无阻流入下游 ListHandler,奠定了无鉴权调用的链路前提。", "file": "server/routes.go", "line": 1506}, {"code": "func (s *Server) ListHandler(c *gin.Context) {", "desc": "随后,路由分发将控制流从 r.GET 注册的处理入口转移至此函数体,ListHandler 作为 callee 开始执行。函数签名接收 gin.Context 但全程不调用任何鉴权逻辑,直接进行模型数据查询与响应构造,使漏洞路径在调用栈中层无阻延续至数据输出。", "file": "server/routes.go", "line": 1217}, {"code": "\tc.JSON(http.StatusOK, api.ListResponse{Models: models})", "desc": "在调用栈下游,c.JSON 将 ListHandler 已组装的模型列表序列化为 JSON 并以 HTTP 200 写回,完成从路由注册入口到数据输出的完整链路闭合。作为 trace 的末端节点,此行是信息在当前请求周期内离开系统的确切时刻,与入口处的无鉴权绑定共同界定了整条漏洞路径的边界。", "file": "server/routes.go", "line": 1266}], "verify": 1, "vuln_category_l1": "业务逻辑", "vuln_category_l2": "BL-AUTHZ-MISSING(授权缺失)", "vuln_ids": ["CVE-2025-63389", "GHSA-F6MR-38G8-39RG"], "vuln_title": "Ollama 多个 API 端点缺少服务端认证导致未授权模型管理操作 (CVE-2025-63389) - routes.go"} +{"commit": "7325791599409de52534429897481918717a9e85", "critical_operation": {"code": "func (s *Server) CreateHandler(c *gin.Context) {", "desc": "CreateHandler 函数在接收 gin 框架传递的请求上下文后,立即进入模型创建的核心逻辑处理,其函数签名本身不包含任何权限前置检查。在整个漏洞利用链路中,此函数是未经验证的请求抵达实质性操作的终点,由于路由层未拦截,任何请求均可无障碍执行到此处并完成模型创建。", "file": "server/create.go", "line": 44}, "entry_id": "entry-00077", "entry_point": {"code": "\tr.POST(\"/api/create\", s.CreateHandler)", "desc": "这一行路由注册语句将 HTTP POST 方法与路径 /api/create 绑定到 CreateHandler 处理函数,是外部请求进入模型创建流程的唯一入口。在漏洞链路中,路由器在挂载该端点时未附加任何凭据验证中间件,致使所有到达此路由的 POST 请求均绕过认证直接转发至业务逻辑。", "file": "server/routes.go", "line": 1517}, "origin": "GitHub Advisory Database (reviewed)", "project": "ollama", "repo_url": "https://github.com/ollama/ollama", "report_id": "GHSA-F6MR-38G8-39RG", "source_link": "https://github.com/advisories/GHSA-f6mr-38g8-39rg", "trace": [{"code": "\tr.POST(\"/api/create\", s.CreateHandler)", "desc": "路由注册语句作为调用链的起点,将来自网络侧的 POST /api/create 请求与 CreateHandler 处理器关联,完成从 HTTP 请求分发层向业务函数层的第一段跳转;随后 gin 框架将在未经认证校验的情况下把请求上下文直接投递至下游处理函数。", "file": "server/routes.go", "line": 1517}, {"code": "func (s *Server) CreateHandler(c *gin.Context) {", "desc": "在调用栈下游,CreateHandler 函数体作为调用链的终点接收来自路由器转发的 gin.Context,进入模型创建的实际执行阶段。由于上游路由注册阶段已放弃认证拦截,此处函数入口处同样缺乏权限核验逻辑,整条调用路径因此形成贯通的权限空洞。", "file": "server/create.go", "line": 44}], "verify": 1, "vuln_category_l1": "业务逻辑", "vuln_category_l2": "BL-AUTHZ-MISSING(授权缺失)", "vuln_ids": ["CVE-2025-63389", "GHSA-F6MR-38G8-39RG"], "vuln_title": "Ollama 多个 API 端点缺少服务端认证导致未授权模型管理操作 (CVE-2025-63389) - create.go"} +{"commit": "7325791599409de52534429897481918717a9e85", "critical_operation": {"code": "func (s *Server) GenerateHandler(c *gin.Context) {", "desc": "GenerateHandler 是绑定到 POST /api/generate 路由的核心处理函数,接收 gin.Context 后立即开始计时并对请求体执行 ShouldBindJSON 解析,直接进入推理流程。函数入口处不存在任何身份验证或会话校验逻辑,路由层透传过来的未认证请求可直接到达此处并触发 GPU 推理资源消耗。在漏洞链路中,GenerateHandler 是攻击面的终点:一旦 r.POST 注册的路由被命中,此函数无条件执行推理操作,最终导致计算资源被任意滥用。", "file": "server/routes.go", "line": 176}, "entry_id": "entry-00078", "entry_point": {"code": "r.POST(\"/api/generate\", s.GenerateHandler)", "desc": "r.POST 将路径 /api/generate 与 GenerateHandler 绑定,完成路由注册并将推理接口暴露在公共 HTTP 层。注册时路由器挂载的全局中间件仅为跨域处理与 allowedHostsMiddleware 的 Host 头校验,未插入任何服务端身份验证拦截器。这一配置缺口是漏洞链路的起点:外部 HTTP 请求不携带任何凭据即可直接命中此路由,随后由 GenerateHandler 无条件执行推理逻辑,构成关键功能缺少认证的根本原因。", "file": "server/routes.go", "line": 1524}, "origin": "GitHub Advisory Database (reviewed)", "project": "ollama", "repo_url": "https://github.com/ollama/ollama", "report_id": "GHSA-F6MR-38G8-39RG", "source_link": "https://github.com/advisories/GHSA-f6mr-38g8-39rg", "trace": [{"code": "r.POST(\"/api/generate\", s.GenerateHandler)", "desc": "作为漏洞调用链的首段,r.POST 在路由表中建立从 POST /api/generate 到 GenerateHandler 的静态映射,扮演 caller 到 callee 的分发桥梁。路由注册时框架仅在链上嵌入了跨域与 Host 头两项粗粒度前置过滤器,缺少认证中间件。随后当任意客户端发起请求时,gin 路由器依据此映射将执行权直接移交给 GenerateHandler,跳过了本应拦截未授权请求的鉴权节点,使漏洞链路得以贯通。", "file": "server/routes.go", "line": 1524}, {"code": "func (s *Server) GenerateHandler(c *gin.Context) {", "desc": "随后,在调用栈下游,GenerateHandler 接收来自 gin 路由分发的 HTTP 请求,成为链路的终端 callee。函数开头调用 ShouldBindJSON 直接解析请求体,既未检查 Authorization 头的合法性,也未调用任何令牌验证接口。至此,从路由注册到推理执行的完整漏洞调用链闭合:路由器将未认证请求透传至此,GenerateHandler 无条件消费并调度模型推理,暴露出 GPU 计算资源与模型权限被任意访问的风险。", "file": "server/routes.go", "line": 176}], "verify": 1, "vuln_category_l1": "业务逻辑", "vuln_category_l2": "BL-AUTHZ-MISSING(授权缺失)", "vuln_ids": ["CVE-2025-63389", "GHSA-F6MR-38G8-39RG"], "vuln_title": "Ollama 多个 API 端点缺少服务端认证导致未授权模型管理操作 (CVE-2025-63389) - routes.go"} +{"commit": "7325791599409de52534429897481918717a9e85", "critical_operation": {"code": "\t\tif err := PushModel(ctx, name.DisplayShortest(), regOpts, fn); err != nil {", "desc": "PushModel 调用位于 PushHandler 内部深层,以 name.DisplayShortest() 传入规范化后的模型标识,并将请求体解析出的目标 Registry 地址和凭据封装入 regOpts 后触发真实上传。由于整条 PushHandler 执行路径上均未插入令牌验证逻辑,regOpts 所携带的目标与参数完全由未经鉴权的请求方掌控,使本次调用成为漏洞链路中将本地模型权重推送至外部 Registry 的最终危害动作。", "file": "server/routes.go", "line": 941}, "entry_id": "entry-00079", "entry_point": {"code": "\tr.POST(\"/api/push\", s.PushHandler)", "desc": "r.POST(\"/api/push\", s.PushHandler) 在路由初始化阶段将 POST /api/push 端点绑定至 PushHandler,构成整个漏洞链路的暴露面入口。此注册语句所处的中间件链仅包含 CORS 处理与 allowedHostsMiddleware(依据 Host 头做粗粒度来源校验),服务端完全未挂载身份认证中间件;任何能在网络层触达此地址的 HTTP 客户端均可不携带凭据地直接进入模型推送流程,使该路由成为未授权访问的起点。", "file": "server/routes.go", "line": 1504}, "origin": "GitHub Advisory Database (reviewed)", "project": "ollama", "repo_url": "https://github.com/ollama/ollama", "report_id": "GHSA-F6MR-38G8-39RG", "source_link": "https://github.com/advisories/GHSA-f6mr-38g8-39rg", "trace": [{"code": "\tr.POST(\"/api/push\", s.PushHandler)", "desc": "r.POST(\"/api/push\", s.PushHandler) 是本条调用链的起始节点,在服务启动阶段将 POST /api/push 路径与 PushHandler 关联注册至路由器。此处未附加任何认证中间件,外部 HTTP 请求命中该路由后将被路由器直接分派给 PushHandler,无需携带有效凭据,标志着未授权推送流程的入口被无条件开放,后续所有链路节点均承接自此处的零鉴权状态。", "file": "server/routes.go", "line": 1504}, {"code": "func (s *Server) PushHandler(c *gin.Context) {", "desc": "PushHandler 是从路由分派跳转至业务逻辑的第一个处理函数,控制流由 trace[0] 的路由注册流转至此,接收 gin.Context 作为请求载体。函数入口处直接执行请求体 JSON 绑定与模型名称解析,未插入任何令牌校验或会话验证步骤。随后,PushHandler 构建 regOpts 并向下游发起 PushModel 调用,将未经鉴权的请求参数无过滤地传递给推送逻辑,充当暴露面与危害动作之间的传导中间环节。", "file": "server/routes.go", "line": 899}, {"code": "\t\tif err := PushModel(ctx, name.DisplayShortest(), regOpts, fn); err != nil {", "desc": "在调用栈下游,PushModel 通过 name.DisplayShortest() 与 regOpts 获取完整的推送目标和 Registry 配置后,直接触发将模型权重上传至外部 Registry 的操作。此步骤紧承 trace[1] 中 PushHandler 的参数组装逻辑,不再感知调用方的身份状态,对所有抵达此处的请求一视同仁地执行上传,是整条未授权访问链路从网络入口延伸至外部数据泄露的最终落点。", "file": "server/routes.go", "line": 941}], "verify": 1, "vuln_category_l1": "业务逻辑", "vuln_category_l2": "BL-AUTHZ-MISSING(授权缺失)", "vuln_ids": ["CVE-2025-63389", "GHSA-F6MR-38G8-39RG"], "vuln_title": "Ollama 多个 API 端点缺少服务端认证导致未授权模型管理操作 (CVE-2025-63389) - routes.go"} +{"commit": "a20541cd9d0a54dbff0b9112f1299c64347370ff", "critical_operation": {"code": "\t\tmcpClient, err = client.NewStdioMCPClientWithOptions(", "desc": "在 NewMCPClient 的 stdio 分支中,NewStdioMCPClientWithOptions 接收从数据库读取并经调用链传递的 command 与 args 字段,通过 stdio transport 以 os/exec 启动子进程——这是整条漏洞链路的命令执行终点,恶意字符串在此真正落地运行。", "file": "internal/mcp/client.go", "line": 133}, "entry_id": "entry-00080", "entry_point": {"code": "\tif err := c.ShouldBindJSON(&service); err != nil {", "desc": "在 CreateMCPService handler 中,ShouldBindJSON 将 HTTP 请求体反序列化到 service 结构体,stdio_config.command 与 args 字段以用户可控的原始值首次进入服务端数据流,缺乏白名单过滤,构成命令注入漏洞链路的污点入口。", "file": "internal/handler/mcp_service.go", "line": 41}, "origin": "GitHub Advisory Database (reviewed)", "project": "Tencent/WeKnora", "repo_url": "https://github.com/Tencent/WeKnora", "report_id": "GHSA-78H3-63C4-5FQC", "source_link": "https://github.com/advisories/GHSA-78h3-63c4-5fqc", "trace": [{"code": "\tif err := c.ShouldBindJSON(&service); err != nil {", "desc": "调用链的起点:ShouldBindJSON 在此将请求体绑定到 service 结构体,stdio_config.command 与 args 作为未经校验的污点字段首次进入 handler 层。随后执行流将经由业务层和 mcp 层逐步传递,直至触发子进程执行。", "file": "internal/handler/mcp_service.go", "line": 41}, {"code": "\tresult, err := h.mcpServiceService.TestMCPService(ctx, tenantID, serviceID)\n\tif err != nil {\n\t\tlogger.ErrorWithFields(ctx, err, map[string]interface{}{\"service_id\": secutils.SanitizeForLog(serviceID)})\n\t\tc.JSON(http.StatusOK, gin.H{\n\t\t\t\"success\": true,\n\t\t\t\"data\": types.MCPTestResult{\n\t\t\t\tSuccess: false,\n\t\t\t\tMessage: \"Test failed: \" + err.Error(),\n\t\t\t},\n\t\t})", "desc": "TestMCPService 被 handler 层调用,从上下文中提取 tenantID 与 serviceID 后向下游业务层发出测试请求;这是从 Gin 路由 caller 到 mcpServiceService callee 的控制流转移,触发阶段将数据库中存储的恶意 stdio 配置激活。", "file": "internal/handler/mcp_service.go", "line": "325-334"}, {"code": "\t// Create temporary client for testing\n\tconfig := &mcp.ClientConfig{\n\t\tService: service,\n\t}\n\n\tclient, err := mcp.NewMCPClient(config)", "desc": "在业务层 TestMCPService 中,从数据库取回的 service 对象被封装为 mcp.ClientConfig,随后调用 mcp.NewMCPClient;command 与 args 字段至此仍未净化,完成从 service 层 caller 向 mcp 包 callee 的数据传递,下一步即触发子进程。", "file": "internal/application/service/mcp_service.go", "line": "246-251"}, {"code": "\t\tmcpClient, err = client.NewStdioMCPClientWithOptions(", "desc": "NewStdioMCPClientWithOptions 接收由业务层传入的 command 与 args,通过 stdio transport 触发 os/exec 在服务器上启动子进程;这是调用链的最终执行点,创建阶段写入的恶意字符串在此兑现为 OS 命令执行。", "file": "internal/mcp/client.go", "line": 133}], "verify": 1, "vuln_category_l1": "命令注入", "vuln_category_l2": "OS 命令注入", "vuln_ids": ["CVE-2026-22688", "GHSA-78H3-63C4-5FQC"], "vuln_title": "WeKnora MCP stdio 配置命令注入 (CVE-2026-22688) - client.go"} +{"commit": "a20541cd9d0a54dbff0b9112f1299c64347370ff", "critical_operation": {"code": "\tcase types.MCPTransportStdio:", "desc": "NewMCPClient 内的 types.MCPTransportStdio case 匹配是命令注入链路的关键决策点:一旦传输类型被识别为 stdio,执行流进入该分支,后续代码将直接取用 StdioConfig 中未经校验的 command 与 args 字段,经环境变量整理后驱动底层 stdio 客户端工厂启动子进程。这一 case 分支的选中,标志着不可信输入从存储态进入执行态的转折。", "file": "internal/mcp/client.go", "line": 120}, "entry_id": "entry-00081", "entry_point": {"code": "func (h *MCPServiceHandler) GetMCPServiceTools(c *gin.Context) {", "desc": "GetMCPServiceTools 是 MCPServiceHandler 对外暴露的 HTTP 处理函数,负责响应对 /:id/tools 端点的 GET 请求,从路由参数中解析服务标识符,并将其交给应用服务层执行工具列表查询。作为整条漏洞触发链路的外层起点,该函数的调用将最终导致系统从数据库中读取持久化的 stdio 配置并发起子进程。", "file": "internal/handler/mcp_service.go", "line": 356}, "origin": "GitHub Advisory Database (reviewed)", "project": "Tencent/WeKnora", "repo_url": "https://github.com/Tencent/WeKnora", "report_id": "GHSA-78H3-63C4-5FQC", "source_link": "https://github.com/advisories/GHSA-78h3-63c4-5fqc", "trace": [{"code": "func (h *MCPServiceHandler) GetMCPServiceTools(c *gin.Context) {", "desc": "GetMCPServiceTools 作为 HTTP 请求的第一个接收点,标志着整条调用链从外部触发至底层执行的起始段:函数从路由参数中取得服务 ID,向下调用应用服务层,携带着数据库中已持久化的服务对象沿调用栈向下传递。在漏洞链路中,这一步对应认证用户通过 /tools 端点触发执行流的阶段,后续各层将依次将该数据导向 stdio 子进程的启动。", "file": "internal/handler/mcp_service.go", "line": 356}, {"code": "\t// Get or create client\n\tclient, err := s.mcpManager.GetOrCreateClient(service)\n\tif err != nil {", "desc": "随后,应用服务层通过 s.mcpManager.GetOrCreateClient(service) 将调用从 HTTP 处理层延伸至 MCP 管理组件:该函数以携带未经校验 stdio 配置的服务对象为参数,由 manager 层判断是否需要新建客户端实例。在新建路径上,service.StdioConfig 中的 command 与 args 原始值将随对象继续向下传递,成为最终 stdio 子进程启动命令的直接来源,构成命令注入数据流的中间传输段。", "file": "internal/application/service/mcp_service.go", "line": "321-323"}, {"code": "\tif service.TransportType == types.MCPTransportStdio {\n\t\treturn m.createStdioClient(service)", "desc": "在调用栈下游,manager.go 对 service.TransportType 的类型判断将执行路径明确导向 stdio 分支:检测到传输类型为 types.MCPTransportStdio 后,立即以该服务对象为参数调用 m.createStdioClient(service),将含有未经验证的命令字段的配置一并交给 stdio 客户端工厂。这一分支承担从类型路由到底层实例化的过渡,漏洞链路在此进入最终构造阶段。", "file": "internal/mcp/manager.go", "line": "47-48"}, {"code": "\tcase types.MCPTransportStdio:\n\t\tif config.Service.StdioConfig == nil {\n\t\t\treturn nil, fmt.Errorf(\"stdio_config is required for stdio transport\")\n\t\t}\n\n\t\t// Convert env vars map to []string format (KEY=value)\n\t\tenvVars := make([]string, 0, len(config.Service.EnvVars))\n\t\tfor key, value := range config.Service.EnvVars {\n\t\t\tenvVars = append(envVars, fmt.Sprintf(\"%s=%s\", key, value))", "desc": "client.go 的 types.MCPTransportStdio 分支负责对 stdio 传输配置进行就绪性验证与参数整理:首先确认 StdioConfig 不为空,随即将 config.Service.EnvVars 中的键值对展开为 KEY=value 格式的字符串切片。这一预处理阶段是漏洞链路的临界环节,未经任何白名单或格式校验的 command 与 args 在此与环境变量汇集,最终被传入底层 stdio 客户端工厂以启动子进程,完成从受污染配置到命令执行的最终转化。", "file": "internal/mcp/client.go", "line": "120-128"}], "verify": 1, "vuln_category_l1": "命令注入", "vuln_category_l2": "OS 命令注入", "vuln_ids": ["CVE-2026-22688", "GHSA-78H3-63C4-5FQC"], "vuln_title": "WeKnora MCP stdio 配置命令注入 (CVE-2026-22688) - client.go"} +{"commit": "008cd8d08369ad188625fb5efaa6fa3707c1548e", "critical_operation": {"code": "\tbefore: [],", "desc": "before: [] 将 Tournament 实例的前置 AST 钩子数组置为空,使表达式送入引擎前不经历任何转换处理。若此数组包含 FunctionThisSanitizer 等钩子,function(){} 的 this 上下文将被重绑定到受控对象;钩子数组为空导致 this 绑定替换步骤完全缺失,是沙盒逃逸漏洞的直接根因,也是整条链路中配置性缺陷的核心所在。", "file": "packages/workflow/src/expression-evaluator-proxy.ts", "line": 10}, "entry_id": "entry-00082", "entry_point": {"code": "import { evaluateExpression, setErrorHandler } from './expression-evaluator-proxy';", "desc": "expression.ts 顶部通过模块导入将 evaluateExpression 与 setErrorHandler 引入 Expression 类的命名空间,使该类的全部表达式求值逻辑均委托给 expression-evaluator-proxy 模块。在漏洞链路中,此导入建立了用户可控表达式从上层业务代码流入 Tournament 求值引擎的模块边界,是整条调用链的起始跨模块依赖点。", "file": "packages/workflow/src/expression.ts", "line": 6}, "origin": "GitHub Advisory Database (reviewed)", "project": "n8n", "repo_url": "https://github.com/n8n-io/n8n", "report_id": "GHSA-V98V-FF95-F3CP", "source_link": "https://github.com/advisories/GHSA-v98v-ff95-f3cp", "trace": [{"code": "import { evaluateExpression, setErrorHandler } from './expression-evaluator-proxy';", "desc": "导入语句是调用链的起始节点,它将 evaluateExpression 符号从 expression-evaluator-proxy 模块引入 expression.ts 的作用域。从 caller 视角看,Expression 类中所有对表达式求值的请求均须经此边界流向代理模块,在漏洞链路中这一跨模块引用将上层工作流执行逻辑与含有配置缺陷的 Tournament 实例绑定在一起。", "file": "packages/workflow/src/expression.ts", "line": 6}, {"code": "\t\t}", "desc": "此闭合括号位于 Expression 类内部某一条件分支结构的末尾,是控制流完成局部判断后向调用栈下游延续的切换点。随后执行路径在 Expression 类内部继续向前推进,从辅助性的类型处理逻辑过渡到负责驱动表达式求值的核心方法;在漏洞链路中,此节点充当从上游辅助逻辑向 resolveSimpleParameterValue 调用路径收敛的中间过渡。", "file": "packages/workflow/src/expression.ts", "line": 205}, {"code": "\tresolveSimpleParameterValue(", "desc": "resolveSimpleParameterValue 是 Expression 类在运行时解析工作流节点参数的核心入口,负责识别以 = 开头的表达式字符串并触发求值流程。在调用栈下游,该方法将用户提供的原始表达式传入 evaluateExpression,是漏洞链路中攻击者注入的恶意表达式从工作流参数层首次进入代理求值层的关键跃迁点。", "file": "packages/workflow/src/expression.ts", "line": 235}, {"code": "export const evaluateExpression: Evaluator = (expr, data) => {", "desc": "evaluateExpression 是 expression-evaluator-proxy 模块暴露给调用方的代理求值函数,接收来自 resolveSimpleParameterValue 传入的表达式字符串与运行时数据。随后该函数不附加任何安全校验,将调用直接转发给内部 evaluator 句柄;在漏洞链路中,这一函数是 caller 到 Tournament 引擎的透传层,其执行结果完全受引擎初始化时的钩子配置左右。", "file": "packages/workflow/src/expression-evaluator-proxy.ts", "line": 19}, {"code": "const evaluator: Evaluator = tournamentEvaluator.execute.bind(tournamentEvaluator);", "desc": "const evaluator 在模块加载阶段通过 bind 将 tournamentEvaluator.execute 固定绑定到 Tournament 实例,生成供后续所有求值请求复用的执行句柄。在调用栈下游,evaluateExpression 的每次运行时调用均经由此句柄进入引擎;由于被绑定的 Tournament 实例携带 before: [] 配置,通过此句柄发起的全部求值均继承了缺失前置 AST 转换的漏洞状态。", "file": "packages/workflow/src/expression-evaluator-proxy.ts", "line": 13}, {"code": "const tournamentEvaluator = new Tournament(errorHandler, undefined, undefined, {", "desc": "new Tournament(errorHandler, undefined, undefined, {...}) 在模块加载期间静态构造求值引擎实例,并将第四个参数中的钩子配置对象一次性写入实例状态。这是漏洞链路的静态初始化节点:before 字段的值在此时被固定,并随后通过 bind 封装为执行句柄,对该模块生命周期内的全部表达式求值持续生效。", "file": "packages/workflow/src/expression-evaluator-proxy.ts", "line": 9}, {"code": "\tbefore: [],", "desc": "before: [] 作为 Tournament 构造函数第四个参数配置对象中的前置钩子数组,是调用链末端的漏洞根节点。在调用栈下游,Tournament 引擎对每条表达式求值前都会遍历此数组以执行 AST 预处理;数组为空导致 function(){} 中 this 的上下文重绑定步骤从未发生,this 在运行时保持指向 Node.js 全局对象,为通过 this 访问系统级接口打开了路径。", "file": "packages/workflow/src/expression-evaluator-proxy.ts", "line": 10}], "verify": 1, "vuln_category_l1": "代码注入", "vuln_category_l2": "表达式注入(Expression Injection)", "vuln_ids": ["CVE-2025-68613", "GHSA-V98V-FF95-F3CP"], "vuln_title": "n8n Expression Injection leading to Remote Code Execution via Unbound function() this Context - expression-evaluator-proxy.ts"} +{"commit": "008cd8d08369ad188625fb5efaa6fa3707c1548e", "critical_operation": {"code": "\treturn value !== null && typeof value === 'object' && value.hasOwnProperty(key);", "desc": "hasKey 函数的返回语句通过 value.hasOwnProperty(key) 对属性名执行存在性校验并将结果返回给调用方。isSafeObjectProperty 将此返回值与 unsafeObjectProperties 阻断名单共同用于判定属性访问是否安全;由于 mainModule、binding、_load 等属性名缺席该名单,hasKey 对这些名称正常返回而非引发拒绝,使得 PrototypeSanitizer 的阻断逻辑被悄然绕过,成为漏洞链路上的终端失守节点。", "file": "packages/workflow/src/utils.ts", "line": 332}, "entry_id": "entry-00083", "entry_point": {"code": "\tstatic initializeGlobalContext(data: IDataObject) {", "desc": "initializeGlobalContext 是 Expression 类的静态方法,接收外部传入的 data 对象并将其设置为工作流表达式的全局求值上下文。该方法是整条漏洞利用链的起始节点:Tournament 引擎随后将基于此上下文对用户注入的表达式求值,而 before 钩子数组为空意味着 function(){} 的 this 绑定在到达此方法时已无法由前置步骤重定向,为后续沙盒逃逸埋下结构性隐患。", "file": "packages/workflow/src/expression.ts", "line": 56}, "origin": "GitHub Advisory Database (reviewed)", "project": "n8n", "repo_url": "https://github.com/n8n-io/n8n", "report_id": "GHSA-V98V-FF95-F3CP", "source_link": "https://github.com/advisories/GHSA-v98v-ff95-f3cp", "trace": [{"code": "\tstatic initializeGlobalContext(data: IDataObject) {", "desc": "从调用方流向 initializeGlobalContext 的入口:外部调用触发该静态方法,data 对象在此时作为空容器开始接收 JavaScript 内置绑定,随后将被逐步填充为完整的求值上下文。作为 trace 的第一跳,这一节点与 entry_point 同处于 expression.ts,标志着沙盒数据构建链路由此展开,后续钩子配置与属性校验均以此为源头。", "file": "packages/workflow/src/expression.ts", "line": 56}, {"code": "\tafter: [PrototypeSanitizer, DollarSignValidator],", "desc": "expression-evaluator-proxy.ts 中,Tournament 实例的 after 数组配置了 PrototypeSanitizer 和 DollarSignValidator,而 before 数组为空,FunctionThisSanitizer 从未被注册。随后在表达式求值阶段,function(){} 表达式的 this 上下文因缺少前置转换步骤而未被重绑定到受控对象,保留了对 Node.js 全局作用域的完整引用,构成沙盒逃逸的结构性根因。", "file": "packages/workflow/src/expression-evaluator-proxy.ts", "line": 11}, {"code": "export const PrototypeSanitizer: ASTAfterHook = (ast, dataNode) => {", "desc": "PrototypeSanitizer 作为 ASTAfterHook 被声明,在 AST 构建完成后接收 ast 与 dataNode 并遍历属性访问节点。在调用栈下游,该钩子针对每个 MemberExpression 节点调用 isSafeObjectProperty 进行属性名校验;然而由于 before 阶段缺少 FunctionThisSanitizer,function(){} 的 this 绑定已在此之前确定,after 阶段的 AST 节点检查仅能作为补充防线而非根本阻断。", "file": "packages/workflow/src/expression-sandboxing.ts", "line": 121}, {"code": "\t\t\t\tif (!isSafeObjectProperty(node.property.name)) {", "desc": "PrototypeSanitizer 内部对每个 MemberExpression 节点执行 isSafeObjectProperty(node.property.name) 校验,若返回 false 则抛出异常阻断访问。这一调用是从 expression-sandboxing.ts 流向 utils.ts 的关键接口点:属性名 mainModule、binding、_load 因未被列入 unsafeObjectProperties 集合,此处校验返回 true 并放行,危险属性访问得以通过整条防御链继续执行。", "file": "packages/workflow/src/expression-sandboxing.ts", "line": 134}, {"code": " * setting untrusted properties can alter the object's prototype chain and introduce vulnerabilities.", "desc": "utils.ts 中的 JSDoc 注释明确警示:允许设置不受信任的属性可能改变对象原型链并引入漏洞。这条注释表明设计层面已意识到原型链污染风险,并在代码中配套设置了 unsafeObjectProperties 阻断名单。然而该名单的实际覆盖范围与注释所揭示的防御意图存在差距,mainModule、binding、_load 等可构成 RCE 路径的属性名未被纳入,注释的安全承诺在运行时无法完全兑现。", "file": "packages/workflow/src/utils.ts", "line": 339}, {"code": "\treturn value !== null && typeof value === 'object' && value.hasOwnProperty(key);", "desc": "从 isSafeObjectProperty 调用链下游到达 hasKey 函数返回点:value.hasOwnProperty(key) 以原生属性查询验证 key 是否直接存在于目标对象。上层 PrototypeSanitizer 依赖此处结果决定是否拦截属性访问;由于 mainModule 等属性名不在 unsafeObjectProperties 名单内,isSafeObjectProperty 对这些名称返回 true,hasKey 正常返回而非阻断,令 this.process.mainModule.require 的完整访问路径在沙盒内得以执行。", "file": "packages/workflow/src/utils.ts", "line": 332}], "verify": 1, "vuln_category_l1": "代码注入", "vuln_category_l2": "表达式注入(Expression Injection)", "vuln_ids": ["CVE-2025-68613", "GHSA-V98V-FF95-F3CP"], "vuln_title": "n8n Expression Injection leading to Remote Code Execution via Unbound function() this Context - utils.ts"} +{"commit": "60670e1e40d304a4c46687a20ecaaf239e729632", "critical_operation": {"code": "\t\t\tres.send(bodyToSend);", "desc": "此处是整条利用链的终端输出:经沙盒包裹后的 HTML 响应体以 bodyToSend 的形式通过 res.send 写入 HTTP 响应流,发往发起请求的浏览器客户端。由于上游 sandboxHtmlResponse 生成的 iframe 携带了允许用户手势触发顶层窗口导航的 sandbox 令牌,浏览器渲染此响应后即为沙盒内脚本提供了逃逸条件。服务端在此步骤不附加任何 CSP 头,漏洞在此完成最终暴露。", "file": "packages/cli/src/webhooks/webhook-request-handler.ts", "line": 150}, "entry_id": "entry-00084", "entry_point": {"code": "\t\t\t\tif (hasHtmlContentType || !headers['content-type']) {", "desc": "RespondToWebhook 节点对外发 HTML 类型响应时,在此处检查响应头中 content-type 是否指向 HTML 或完全缺失。条件成立后,代码进入专用的 HTML 沙盒处理分支,将原始响应体交给 sandboxHtmlResponse 进行 iframe 封装。这是整条漏洞利用链的起点判断:攻击者控制的 HTML 内容一旦通过此门控,便会被包裹进一个带有过宽 sandbox 属性的 iframe 中,为后续沙盒逃逸埋下隐患。", "file": "packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.ts", "line": 485}, "origin": "GitHub Advisory Database (reviewed)", "project": "n8n", "repo_url": "https://github.com/n8n-io/n8n", "report_id": "GHSA-58JC-RCG5-95F3", "source_link": "https://github.com/advisories/GHSA-58jc-rcg5-95f3", "trace": [{"code": "\t\t\t\tif (hasHtmlContentType || !headers['content-type']) {", "desc": "调用链起点,RespondToWebhook 节点执行阶段在此判断当前响应的 content-type 是否为 HTML 类型或缺省。此分支条件是从节点主执行逻辑(caller)流向 HTML 沙盒封装路径(callee)的第一道门:条件为真时,下一行立即调用 sandboxHtmlResponse,攻击者控制的 HTML 内容从此刻起进入 iframe 构造流程,触发沙盒属性过宽问题的前提在此成立。", "file": "packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.ts", "line": 485}, {"code": "\t\t\t\t\tresponseBody = sandboxHtmlResponse(rawBody);", "desc": "紧接前一步的条件判断,节点将原始响应体 rawBody 直接传入 sandboxHtmlResponse,并将返回值赋给 responseBody。此调用标志着控制流从 RespondToWebhook 节点(caller)跨模块流入 html-sandbox.ts 中的封装函数(callee):攻击者控制的 HTML 内容在此进入 iframe 构建阶段,随后将被嵌入带有过宽 sandbox 属性集的 srcdoc iframe 中。", "file": "packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.ts", "line": 486}, {"code": "\tconst escapedHtml = text.replaceAll('&', '&').replaceAll('\"', '"');", "desc": "随后在 sandboxHtmlResponse 内部,对输入文本进行局部字符转义:将 & 替换为 HTML 实体,将双引号替换为 ",以防止内容破坏外层 srcdoc 属性的引号边界。此转义步骤仅保护属性语法完整性,不能阻止 iframe 内脚本执行;真正的防御缺口在于 sandbox 属性令牌集合的配置,而非此处的字符替换。转义后的内容将被嵌入 srcdoc,进入 iframe 元素构造的下一步。", "file": "packages/core/src/html-sandbox.ts", "line": 47}, {"code": "\t\t\tstyle=\"position:fixed; top:0; left:0; width:100vw; height:100vh; border:none; overflow:auto;\"", "desc": "在调用栈内部 sandboxHtmlResponse 构造 iframe 元素时,此行写入全屏覆盖样式:iframe 被固定定位并撑满整个视口,视觉上与普通页面无从区分。样式本身不直接引入安全漏洞,但全屏布局紧邻 sandbox 属性定义,二者共同使攻击者能够构造用户无法察觉的欺骗性界面,诱导用户产生触发顶层窗口导航所需的手势交互,从而激活沙盒逃逸条件。", "file": "packages/core/src/html-sandbox.ts", "line": 50}, {"code": "\t\t\tresponse = {", "desc": "sandboxHtmlResponse 返回封装后的 iframe 字符串后,控制流回到 RespondToWebhook 节点,此处以对象字面量形式组装最终 response,将沙盒化的响应体、HTTP 状态码及响应头纳入其中。此步骤标志着从 html-sandbox.ts(callee)返回节点主逻辑(caller),漏洞载荷完成构建并进入传递阶段:组装好的 response 将在下一步被移交给运行时的发送机制。", "file": "packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.ts", "line": 576}, {"code": "\t\t\t\tthis.sendResponse(response);", "desc": "response 对象组装完毕后,节点调用 this.sendResponse 将其向上传递给 n8n 运行时的 webhook 基础设施。此调用是从节点执行层(caller)跨层流向请求处理器层(callee)的边界:携带过宽 sandbox 属性的 iframe 响应内容至此离开节点作用域,进入 webhook-request-handler.ts 的控制流,后续处理器将最终决定该响应如何写入 HTTP 流并发往客户端。", "file": "packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.ts", "line": 583}, {"code": "\t\t\tif (isWebhookResponse(response)) {", "desc": "请求处理器收到响应对象后,首先检查其是否满足 isWebhookResponse 的结构谓词。此条件判断将处理器主逻辑(caller)的控制流导向专用 webhook 响应发送路径(callee),与上游节点层的 sendResponse 调用形成衔接。分支成立时,程序随即执行对 sendWebhookResponse 的异步调用,携带 iframe 载荷的响应对象继续沿调用链向下游流动。", "file": "packages/cli/src/webhooks/webhook-request-handler.ts", "line": 69}, {"code": "\t\t\t\tawait this.sendWebhookResponse(res, response);", "desc": "随后处理器以 await 异步调用 sendWebhookResponse,将 Express res 对象与结构化 webhook 响应一并传入。此调用是从请求处理主逻辑(caller)流向专职发送方法(callee)的入口;沙盒化的响应体在此进入最后一段 HTTP 写入前的处理逻辑,包含内容类型检测和可能的二次沙盒化决策,是漏洞载荷从传递阶段进入最终发送阶段的关键跨步。", "file": "packages/cli/src/webhooks/webhook-request-handler.ts", "line": 70}, {"code": "\t\tif (isWebhookStaticResponse(webhookResponse)) {", "desc": "在调用栈下游 sendWebhookResponse 内部,isWebhookStaticResponse 谓词检查当前 webhookResponse 是否为静态响应类型。分支成立后,控制流进入处理静态响应体的代码段,其中包括内容类型检测、沙盒化决策及最终的 res.send 调用。此步骤将执行路径从通用分发逻辑收窄至漏洞核心代码段,为后续 needsSandbox 计算和 sandboxHtmlResponse 调用提供执行前提。", "file": "packages/cli/src/webhooks/webhook-request-handler.ts", "line": 112}, {"code": "\t\t\tconst needsSandbox = !contentType || isHtmlRenderedContentType(contentType);", "desc": "在调用栈最底层的静态响应处理段,代码检测 contentType 是否为空或指向需要渲染的 HTML 类型,并将布尔结果写入 needsSandbox。此判断与 RespondToWebhook 节点侧的 HTML 类型检测相互呼应,构成漏洞链路上的第二个沙盒决策点;当 needsSandbox 为真时,下一行将再次调用 sandboxHtmlResponse,生成携带过宽 sandbox 属性的 iframe 并作为 bodyToSend 发往客户端。", "file": "packages/cli/src/webhooks/webhook-request-handler.ts", "line": 148}, {"code": "\t\t\tconst bodyToSend = needsSandbox ? sandboxHtmlResponse(body) : body;", "desc": "基于上一步 needsSandbox 的计算结果,三元表达式决定最终的 bodyToSend:若需要沙盒则调用 sandboxHtmlResponse(body) 生成 iframe 包裹,否则直接传递原始 body。此处是 sendWebhookResponse(caller)再次进入 sandboxHtmlResponse(callee)的入口;该函数在此生成的 iframe 所携带的 sandbox 属性权限集过宽,是漏洞在发送阶段完成最终构造的关键节点,随后一行立即将其写入响应流。", "file": "packages/cli/src/webhooks/webhook-request-handler.ts", "line": 149}, {"code": "\t\t\tres.send(bodyToSend);", "desc": "最终,res.send 将 bodyToSend 写入 HTTP 响应流并发往客户端,完成整条调用链的末端输出,与 critical_operation 锚点位置重合。携带过宽 sandbox 权限的 iframe 内容在此刻离开服务端控制范围;浏览器渲染该 iframe 后,沙盒内脚本借助用户手势触发顶层窗口导航,实现对顶层上下文的脚本注入。服务端在此步骤未设置任何 CSP 头,无二次防线拦截。", "file": "packages/cli/src/webhooks/webhook-request-handler.ts", "line": 150}], "verify": 1, "vuln_category_l1": "XSS", "vuln_category_l2": "Stored XSS", "vuln_ids": ["CVE-2025-61914", "GHSA-58JC-RCG5-95F3"], "vuln_title": "n8n Stored XSS in 'Respond to Webhook' Node via iframe Sandbox Bypass - webhook-request-handler.ts"} +{"commit": "60670e1e40d304a4c46687a20ecaaf239e729632", "critical_operation": {"code": "\t\t\t\tres.send(sandboxHtmlResponse(body));", "desc": "res.send(sandboxHtmlResponse(body)) 将 iframe 封装后的响应体写入 HTTP 输出流并发送至客户端。sandboxHtmlResponse 在此处构造带有 sandbox 属性的 srcdoc iframe,但属性集中包含允许用户手势触发顶层导航的令牌,导致 iframe 内脚本可将浏览器顶层窗口重定向至恶意 URI,是漏洞链路的最终执行点。", "file": "packages/cli/src/webhooks/webhook-request-handler.ts", "line": 154}, "entry_id": "entry-00085", "entry_point": {"code": "\t\t\t\tconst responseBodyParameter = this.getNodeParameter('responseBody', 0) as string;", "desc": "responseBodyParameter 由 getNodeParameter('responseBody', 0) 赋值,从节点参数注册表中取出用户定义的响应体字符串。此处是攻击者可控的 HTML 内容进入运行时的最初位置,未经净化的字符串从此开始沿调用栈向下游传递,奠定了漏洞链路全程的输入基础。", "file": "packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.ts", "line": 411}, "origin": "GitHub Advisory Database (reviewed)", "project": "n8n", "repo_url": "https://github.com/n8n-io/n8n", "report_id": "GHSA-58JC-RCG5-95F3", "source_link": "https://github.com/advisories/GHSA-58jc-rcg5-95f3", "trace": [{"code": "\t\t\t\tconst responseBodyParameter = this.getNodeParameter('responseBody', 0) as string;", "desc": "getNodeParameter('responseBody', 0) 从 caller(节点执行上下文的参数注册表)向 callee(节点内部处理逻辑)传递用户定义的响应体字符串,完成对 responseBodyParameter 的赋值。此步骤是漏洞链路在 RespondToWebhook 节点内部的起点,攻击者可控的 HTML 自此进入运行时,随后流向内容类型判断逻辑。", "file": "packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.ts", "line": 411}, {"code": "\t\t\tconst hasHtmlContentType =", "desc": "hasHtmlContentType 在 responseBodyParameter 完成赋值后随即被计算,代表从参数读取阶段向内容类型判断阶段的过渡,是 caller(参数读取逻辑)流向 callee(内容类型分支逻辑)的关键中间量。该布尔值为 true 时将激活下游的沙箱封装条件,在漏洞链路中充当决定 HTML 处理路径是否被触发的分流开关。", "file": "packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.ts", "line": 405}, {"code": "\t\t\t\thasHtmlContentType &&", "desc": "hasHtmlContentType 作为条件表达式的组成部分,标志着控制流从内容类型检测阶段(caller)流向沙箱包装阶段(callee)的转折。当该值为 true 时,执行路径进入 HTML 专用分支,随后 sandboxHtmlResponse 被调用以封装响应体,在漏洞链路中是触发缺陷沙箱包装的条件门控。", "file": "packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.ts", "line": 568}, {"code": "\t\t\t\tresponseBody = sandboxHtmlResponse(JSON.stringify(responseBody as string));", "desc": "responseBody 在此处被 sandboxHtmlResponse 的返回值覆写,标志着从原始字符串阶段(caller 侧的 responseBody)流向 iframe 封装阶段(callee 侧的 sandboxHtmlResponse)的关键转变。JSON.stringify 将响应体序列化后传入包装函数,所生成的 iframe 携带存在权限缺陷的 sandbox 属性,随后向下游的 webhook 请求处理层传递,在漏洞链路中完成节点侧的沙箱包装动作。", "file": "packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.ts", "line": 573}, {"code": "\t\t\tconst needsSandbox = contentType && isHtmlRenderedContentType(contentType);", "desc": "needsSandbox 在 webhook-request-handler 层被赋值,标志着调用链从 RespondToWebhook 节点层(caller)跨越至 webhook 请求处理层(callee)。isHtmlRenderedContentType(contentType) 对内容类型做二次判断,其结果决定是否再次调用 sandboxHtmlResponse;在漏洞链路中,此步骤是到达最终响应发送前的最后一道内容类型分流判断。", "file": "packages/cli/src/webhooks/webhook-request-handler.ts", "line": 152}, {"code": "\t\t\t\tres.send(sandboxHtmlResponse(body));", "desc": "res.send(sandboxHtmlResponse(body)) 是调用链的末端段,从 webhook 请求处理层(caller 侧的处理逻辑)向客户端浏览器(callee 侧的 HTTP 响应接收方)输出最终响应。在调用栈下游,sandboxHtmlResponse 对 body 再次封装为 srcdoc iframe 并经 res.send 写入响应流,使携带恶意脚本的 HTML 抵达受害者浏览器,是漏洞链路中脚本逃逸的最终实现点。", "file": "packages/cli/src/webhooks/webhook-request-handler.ts", "line": 154}], "verify": 1, "vuln_category_l1": "XSS", "vuln_category_l2": "Stored XSS", "vuln_ids": ["CVE-2025-61914", "GHSA-58JC-RCG5-95F3"], "vuln_title": "n8n Stored XSS in 'Respond to Webhook' Node via iframe Sandbox Bypass - webhook-request-handler.ts"} +{"commit": "60670e1e40d304a4c46687a20ecaaf239e729632", "critical_operation": {"code": "streamToSend.pipe(res, { end: false });", "desc": "streamToSend.pipe 在此将经过沙箱变换的字节流写入 HTTP 响应对象 res,完成污点数据从节点层到客户端浏览器的最终交付。上游因 HTML Content-Type 触发 createHtmlSandboxTransformStream 后,此处送出的响应体包含带过度宽松 sandbox 权限的 iframe 封装;浏览器接收后,沙箱内脚本可在用户手势时逃逸并在顶层窗口执行,漏洞危害在此节点完成最终兑现。", "file": "packages/cli/src/webhooks/webhook-request-handler.ts", "line": 133}, "entry_id": "entry-00086", "entry_point": {"code": "if (shouldStream) {", "desc": "shouldStream 条件在 RespondToWebhook 节点文本响应分支被求值,为真时执行流切入流式输出路径,rawBody 中的用户可控 HTML 内容随即通过 sendChunk 传向下游 CLI 层。此处是污点数据跨越模块边界进入流式管道的入口条件,也是下游 iframe 缺陷沙箱封装被激活的前提开关。", "file": "packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.ts", "line": 491}, "origin": "GitHub Advisory Database (reviewed)", "project": "n8n", "repo_url": "https://github.com/n8n-io/n8n", "report_id": "GHSA-58JC-RCG5-95F3", "source_link": "https://github.com/advisories/GHSA-58jc-rcg5-95f3", "trace": [{"code": "if (shouldStream) {", "desc": "trace 链路从此处起步:shouldStream 为真时,RespondToWebhook 节点将 rawBody 中的用户可控 HTML 送入流式输出管道,开启跨模块的污点传播旅程。后续各步骤沿 sendChunk、Content-Type 检测、createHtmlSandboxTransformStream 变换,直至写入 HTTP 响应,共同构成本条漏洞链路的完整路径。", "file": "packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.ts", "line": 491}, {"code": "this.sendChunk('item', 0, rawBody);", "desc": "随后 sendChunk 以 'item' 类型把 rawBody 作为流数据块从节点层发出,完成 RespondToWebhook 节点上下文到 CLI 响应基础设施的跨层投递。在调用栈下游,webhook 请求处理器将接收到该流并依据 Content-Type 决定是否进入 createHtmlSandboxTransformStream 变换分支,这是污点内容穿越模块边界的第一次显式跳转。", "file": "packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.ts", "line": 493}, {"code": "const needsSandbox = contentType && isHtmlRenderedContentType(contentType);", "desc": "在调用栈下游的 webhook 请求处理器中,isHtmlRenderedContentType 检测响应流的 Content-Type 并把布尔结果存入 needsSandbox。该值在下一行决定是否调用 createHtmlSandboxTransformStream 进行沙箱封装;为真时触发的变换器携带过度宽松的 sandbox 权限属性集,使此判断成为漏洞路径的条件激活点。", "file": "packages/cli/src/webhooks/webhook-request-handler.ts", "line": 130}, {"code": "const streamToSend = needsSandbox ? stream.pipe(createHtmlSandboxTransformStream()) : stream;", "desc": "streamToSend 根据 needsSandbox 在两条路径间分叉:HTML 类型时将原始流接入 createHtmlSandboxTransformStream,生成包含 iframe srcdoc 封装的变换流;否则直接引用未包装的原始流。HTML 分支触发的变换器在 suffix 缓冲区硬编码了含 allow-top-navigation-by-user-activation 的宽泛 sandbox 属性,此赋值是缺陷沙箱被引入输出链路的结构性分叉点。", "file": "packages/cli/src/webhooks/webhook-request-handler.ts", "line": 132}, {"code": "const prefix = Buffer.from('