diff --git a/data/entries.trace_fixed.jsonl b/data/entries.trace_fixed.jsonl new file mode 100644 index 0000000..2e9e7c9 --- /dev/null +++ b/data/entries.trace_fixed.jsonl @@ -0,0 +1,408 @@ +{"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('