ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Langfuse 安全审查指南:SSRF、密钥泄露与遥测隐私的代码评审实战

2026/9/10 23:36:08 拓冰建站 浏览量
Langfuse 安全审查指南:SSRF、密钥泄露与遥测隐私的代码评审实战 Langfuse 安全审查指南SSRF、密钥泄露与遥测隐私的代码评审实战【免费下载链接】langfuse Open source AI engineering platform: LLM evals, observability, metrics, prompt management, playground, datasets. Integrates with OpenTelemetry, LangChain, OpenAI SDK, LiteLLM, and more. YC W23项目地址: https://gitcode.com/GitHub_Trending/la/langfuse安全审查Security Review是 Langfuse 仓库中沉淀的一套 Agent 技能它将团队在外部安全报告中反复遇到的漏洞类别整理成可执行的检查流程让未来的 Agent 在设计阶段和评审阶段就能拦截问题而不是等漏洞曝光后再补救。本篇技术指南以 .agents/skills/security-review/SKILL.md 为主体完整讲解这套技能的应用时机、检查清单、三大高发漏洞类别出站 URL 校验 / SSRF、密钥读取路径、客户端遥测隐私及其对应的源码级防御实现读完后你将掌握如何在 Langfuse 中评审 URL 类、密钥类与遥测类改动并知道每一类问题该引用哪些规范助手与已知正确的调用点。一、这套技能解决什么问题Langfuse 是一个开源的 AI 工程平台负责追踪 LLM 调用、评估、提示词管理与数据集管理其代码天然包含大量接受用户输入 URL、发送出站请求、处理跨租户数据、配置第三方集成的功能面。历史上团队在外部安全报告中反复发现过几类漏洞包括SSRF服务器端请求伪造与出站 URL 校验缺失读取路径返回了本应隐藏的密钥字段浏览器遥测与会话回放将客户可控数据发送给了第三方。这套技能正是把这些反复出现的发现沉淀为一份结构化的审查协议触发面覆盖SKILL.md 中列出的改动类型——用户提供的 URL / host /endpoint/baseURL/ webhook 目标、新的出站 HTTP 请求、新的集成表单、变更项目级数据或访问范围的 tRPC 过程与公开 API 路由、密钥与加密字段、重定向与跨域请求头处理、文件上传与图片代理、以及可传输客户可控内容的客户端遥测路径。技能明确要求在plan mode规划模式就应用设计新集成时就要把正确的校验面写进方案而不是留到后续的 CVE 里再补救。二、审查流程从检查清单到主题参考文档技能给出的使用路径分三步详见 SKILL.md打开 references/checklist.md对照改动逐条做心理扫描每命中一个检查项就打开对应的主题参考文档目录刻意保持精简新的发现类别出现并复现后再新增主题文件见扩展这套技能一节。当前的主题参考表如下主题何时打开文件SSRF 与出站 URL 校验改动接受或抓取用户提供的 URL / host / endpointreferences/outbound-url-validation.md密钥读取路径改动改变了某个同时存储凭据的实体或配置块的读取路由返回值references/secret-read-paths.md客户端遥测与会话回放改动将 DOM、浏览器状态、事件、日志或网络数据记录到第三方系统references/client-telemetry-privacy.md检查清单 checklist.md 本身覆盖六个大类用户提供的 URL 与出站请求、租户隔离、密钥与凭据、审计日志、客户端遥测与会话回放、负向测试。每一项都给出了命中即必须存在的缓解措施并把细节下放到主题文档保证清单始终只是一份触发面。三、输出期望评审模式与设计模式技能对两种使用场景给出了不同的输出要求见 SKILL.md评审模式Review Mode先按严重程度排序列出发现并给出文件与行号引用每个发现都要点名作者应当复制的规范助手canonical helper或已知正确调用点对 SSRF 类发现直接指向 outbound-url-validation.md而不是在评审里重新推导修复方案缺失负向测试私有 IP、跨租户、缺 scope本身就要作为发现提出而不是锦上添花。设计 / 规划模式Design / Plan Mode复述新功能暴露了哪些面表单、公开 API 路由、worker 入口点对每个命中检查清单触发器的面指明必须调用哪个校验器、在哪个层级调用保存时、使用时、连接时、重定向时把以后再做校验视为设计缺陷——校验必须和引入该面的改动同批落地。四、SSRF 与出站 URL 校验威胁模型与规范助手4.1 威胁模型任何把用户输入推导出的 URL 作为出站请求目标的代码路径变更表单、公开 API 字段、集成配置、图片代理都可能被胁迫成 SSRF。Langfuse 部署拓扑中的高价值内部目标包括outbound-url-validation.md 明确列出的云实例元数据服务169.254.169.254、IMDSv2 端点、内部 Postgres / ClickHouse / Redis / S3/MinIO / 队列管理 UI、回环管理接口127.0.0.1、localhost、Docker API 的2375/2376端口、Kubernetes API Server 及其他集群内部控制面以及任何可从 Pod 或容器路由到的 RFC1918 / RFC6598 / IPv6 ULA 网段。关键的威胁建模前提即使该面要求integrations:CRUD或管理员权限攻击者模型依然假设持证用户是恶意的或已被攻破。SSRF 让攻击者从应用层管理权限跃迁为部署拓扑本应拒绝的网络层访问。4.2 规范助手packages/shared/src/server/outbound-url/所有规范助手集中在packages/shared/src/server/outbound-url/目录包括 validation.ts、connection.ts、fetch.ts。四个核心原语如下parseOutboundUrl(urlString)validation.ts安全解析。拒绝内嵌凭据url.username/url.password非空即抛url-credentials-not-allowed、非法编码与错误 URL 语法。任何用户提供的 URL 都应使用它而不是new URL(...)。源码中还有一个容易被忽略的细节它故意只校验编码合法性而不解析解码结果因为把整个 URL 先解码可能把编码数据变成分隔符导致校验看到的 hostname 与fetch实际请求的不同。validateOutboundUrlHost({ url, whitelist, logContext, shouldSkipDnsCheckForLiteralIps })validation.ts检查 hostname 黑名单、IP 字面量黑名单并对正向 DNS 解析结果逐一校验 CIDR 黑名单。resolveHost同时执行dns.resolve4、dns.resolve6与dns.lookup({ all: true })把fetch经由getaddrinfo可能看到的 hosts 文件 / NSS 条目也纳入校验从而抵御 DNS 重绑定。DNS 解析失败被视为硬错误dns-lookup-failed——静默放行会让攻击者控制的或 split-horizon DNS 绕过 IP 黑名单。addSecureOutboundConnectionValidation(options, ...)connection.ts给fetch请求挂上连接时connect-timeIP 校验的 dispatcher让 TCP 对端在DNS 解析之后再次被校验而不只是保存时校验一次。其底层通过createSecureOutboundLookup在 undiciAgent的connect.lookup钩子中执行validateOutboundResolvedIp并且按白名单策略缓存独立的 Agent 池上限 32超出即 fail-closed避免宽松策略打开的 socket 被更严格的后续请求复用。fetchWithSecureRedirects(...)fetch.ts手动重定向处理。每个Location跳转都用调用方提供的校验器校验并在跨域重定向时剥离敏感请求头Authorization、Cookie、proxy-authorization及签名头见源码中的SENSITIVE_REDIRECT_HEADERS。该文件还定义了RedirectValidationError带cause保留内层错误的类型化code且通过 options bag 传递以保证不可枚举避免结构化日志序列化被拒绝目标中可能携带的密码、MaxRedirectsExceededError、CircularRedirectError以及redactUrlCredentials工具用于在日志/错误文本中抹掉 URL 中的凭据。4.3 面级封装助手优先复用不要自己造轮子技能文档列出了三个已存在的面级封装LLM base URLvalidateLlmConnectionBaseURL——限制协议为 HTTP/HTTPSCloud 环境强制 HTTPSIP 字面量跳过 DNS 检查以兼容自定义网关Webhook URLvalidateWebhookURLBlob 存储端点validateBlobStorageEndpoint 与配套的blobStorageEndpointConnectionValidationOptions连接时强制经由StorageServiceFactory流入。4.4 必须三层齐备的防御每个出站 URL 面必须同时具备三层这是技能文档的硬性要求保存时校验在持久化该 URL 的变更操作 / tRPC 过程 / 公开 API 路由中校验失败即拒绝写入使用时 / 连接时校验在请求真正发出时worker 任务、惰性校验端点、处理器校验。因为 DNS 在保存与使用之间可能变化真正发起调用的 SDK 解析到的 IP 可能不同于保存时校验看到的 IP必须把addSecureOutboundConnectionValidation或 SDK 的等价钩子穿进请求重定向时校验如果请求可被重定向裸fetch()默认redirect: follow会静默地追着重定向跳进回环网段必须改用fetchWithSecureRedirects配同款校验器。4.5 已知正确调用点可复制LLM base URL 保存llm-api-key/router.ts 的update变更在持久化前调用validateLlmConnectionBaseURLLLM base URL 走公开 APIllm-connections/index.tsWebhook URL 保存与使用validation.ts 同时接入自动化表单与 worker 侧 webhook 发送器Blob 存储端点blobstorage-integration-router.ts 的validate变更调用validateBlobStorageEndpoint连接时强制经StorageServiceFactory.getInstance({ connectionValidation: blobStorageEndpointConnectionValidationOptions() })流入。4.6 新增出站 URL 面的七步流程技能文档给出了新增出站 URL 面的完整步骤识别用户输入层表单变更、公开 API 路由、env 导入任何租户可提供 URL 的地方判断现有封装是否适用——适用则复用否则在packages/shared/src/server/...下新增封装委托给validateOutboundUrlHost使黑名单行为、DNS 重绑定处理与凭据检查保持集中为自托管用户定义每面独立的 env 白名单三元组host / IP / IP 段模仿LANGFUSE_WEBHOOK_WHITELISTED_HOST/IPS/IP_SEGMENTS、LANGFUSE_LLM_CONNECTION_WHITELISTED_HOST/IPS/IP_SEGMENTS、LANGFUSE_BLOB_STORAGE_ENDPOINT_WHITELISTED_HOST/IPS/IP_SEGMENTS。不得与其他面共用白名单将 env 变量加入.env*.example与对应包的env.mjs/ts从三处调用封装写入 URL 的每个 tRPC 变更、接受 URL 的每个公开 API 路由、实际发出请求的 worker / 处理器若底层 SDK 没有暴露 host 校验钩子则使用连接时校验若请求可重定向用fetchWithSecureRedirects以同一封装作为校验器添加服务端测试证明被阻止的目标校验失败127.0.0.1、169.254.169.254、RFC1918 字面量、解析到私网 IP 的 hostnameDNS 重绑定、Cloud 上的http://、以及含user:passhost的 URL。4.7 评审时要标记的反模式未先调用validate*URL助手、或请求选项中未挂addSecureOutboundConnectionValidation就直接fetch(用户提供URL)axios、got同理tRPC 变更在写入前未调用对应校验器就持久化host/endpoint/baseURL/webhookUrl字段StorageServiceFactory.getInstance({ endpoint })或任何接受用户可控 URL 的 SDK 客户端初始化未穿通connectionValidation用new URL(userInput)代替parseOutboundUrl(userInput)做自定义解析对用户可控 URL 用fetch(url)默认redirect: follow跟随重定向应改用fetchWithSecureRedirects新集成 UI 只在客户端校验——保存时校验必须跑在服务端只有保存时校验而没有使用时校验或反之——两层都必需因为 worker 可能在保存数小时后才发出请求届时 DNS 早已变化。4.8 env 白名单行为与负向测试Cloud 环境设置了NEXT_PUBLIC_LANGFUSE_CLOUD_REGION强制严格模式白名单 env 变量被忽略且对要求 HTTPS 的面LLM base URL、blob 存储端点强制 HTTPS。自托管则读取上述每面独立的三元组 env。一个值得注意的现状blob 存储端点校验目前在自托管默认是opt-in——在操作者配置任一白名单 env 变量之前该助手是 no-op。源码中有TODO(next major)计划翻转默认行为在此之前新面不要依赖 blob 存储校验而要自己封装默认严格的 wrapper。负向测试是硬性要求新增出站 URL 面必须包含断言以下各项校验失败的服务端测试回环字面量http://127.0.0.1、http://[::1]、云元数据字面量http://169.254.169.254、RFC1918 字面量http://10.0.0.1、解析到私网 IP 的 hostname、含内嵌凭据的 URLhttp://user:passhost、Cloud 上的http://、以及空白名单在自托管上不允许任何内部目标。测试模式参考 llm-base-url-validation.test.ts 及各封装旁的测试文件如 fetch.test.ts、noProxy.test.ts。五、密钥读取路径允许列表优于删除列表5.1 威胁模型功能级读 scopeautomations:read、integrations:read及同类被包括 VIEWER 在内的每个项目角色持有因此任何读取路径返回的内容项目里权限最低的成员都能读到并且会送到浏览器。以 JSON 列存储的配置块把展示字段和凭据放在同一个对象里webhook 配置在secretKey旁边就是displaySecretKeyrepository-dispatch 配置在githubToken旁边就是displayGitHubToken集成配置在加密请求头旁边就是展示值。关键在于静态加密并不能让这类字段变得安全到可以返回。密文依然扩大了ENCRYPTION_KEY泄露时的爆炸半径而仓库自身的标准从那些被剥离的相邻字段可见就是这些字段永远不应到达客户端。该漏洞类反复出现的形状是净化逻辑按类型逐个编写、接到写入路径上而读取路径保留了一个其他类型的 catch-all 回退分支——该回退静默地把下一种类型的凭据送出去且上面的类型断言把遗漏从编译器眼皮底下藏了起来。5.2 规范助手按类型允许列表的净化器例如 convertToSafeWebhookConfig / convertToSafeGitHubDispatchConfig——它们点名每一个允许离开服务器的字段因此新增字段在有人刻意添加之前不会出现在响应里Safe*schema 定义为FullSchema.omit({ secret fields })让安全类型与字段允许列表Object.keys(SafeXSchema.shape)从同一个声明派生automation-repository.ts 中的成对访问器约定getActionById返回净化后的配置给所有客户端面向的路径getActionByIdWithSecrets返回原始行且只保留给执行路径worker 投递、配置助手列级密钥 db.ts 中客户端范围的omit块默认从每个 Prisma 结果中排除含密钥的列投递路径用显式select重新选择——当密钥是独立列而非 JSON 字段时这比手工剥离更可取。5.3 五项必需防御在共享的 repository 或 domain 转换器中净化而不是在路由里。一个转换器必须服务所有读取路由以及创建/更新响应。路由级净化只覆盖作者记得住的那些面对类型联合做穷尽分发用default分支赋值给never的switchdefault: { const unhandledActionType: never actionType; throw new InternalServerError(unhandled type ${unhandledActionType}); }新的联合成员因此会在缺少净化器时变成编译错误。永远不要写其他或未来类型原样返回存储值的回退分支用允许列表构建安全对象而不是删除已知密钥。删除列表的有效期只到最后一个加字段的人为止解析失败的值要 fail-closed把存储对象投影到安全 schema 的键上而不是原样透传——遗留行和手工编辑过的行恰恰是绕过解析门控净化器的那些。优先投影而不是抛错读取保持可用而密钥依然无法存活以最低权限角色为读取路径添加负向测试对 VIEWER 调用者断言expect(config).not.toHaveProperty(secret)同时覆盖列表路由与单条路由。干净的创建/更新响应证明不了读取路径的安全性——这个不对称性正是该类 bug 能在评审中存活下来的原因。5.4 已知正确调用点convertActionToDomain穷尽 switch、按类型允许列表净化器、解析失败时以允许列表投影兜底它是 automations/server/router.ts 中自动化的读取路由与创建/更新响应背后的唯一转换器automations-trpc.servertest.ts 中的describe(automations read path secret redaction)VIEWER 角色的读取路径负向测试包括一个解析失败的配置。5.5 评审时要标记的反模式用类型断言充当净化config: row.config as SafeActionConfig。名称声称了不变量而断言压制了唯一能强制它的检查。所有as Safe*、as Public*、as Redacted*都要标记如果类型携带安全不变量考虑加 brand 让只有转换器能产出该类型、裸断言无法编译坦然承认行为的回退分支对 X或未来类型原样返回 config这类注释本身就是发现而不是它的上下文把净化后的值写回数据库把Safe*配置往返进prisma.model.update({ data: { config } })会持久化被剥离的形状静默丢弃净化器移除的字段加密头、存储的密钥。写入前要重新读取原始行只覆盖变更响应的测试让读取路由——只读角色唯一能触达的那个——处于未断言状态。六、客户端遥测与会话回放隐私6.1 威胁模型浏览器分析与会话回放即使应用从未在显式分析事件中发送该值也可能把客户可控数据导出给第三方。回放记录器观察渲染后的 DOM、属性、portal、网络元数据以及可选的 console / 自定义事件。仅靠输入掩码保护不了后来被渲染为文本的值合规敏感部署不能因为遥测凭据存在就开始录制。6.2 规范控件web/src/pages/_app.tsx 拥有 PostHog 初始化、部署门控、原生/自定义输入掩码、ph-no-capture块类与网络请求体脱敏共享渲染器应在展示客户可控来源的值时自带ph-no-capture从而保护所有调用点以及子树的文本、属性和嵌套媒体portal 化的悬浮卡片与对话框需要在 portal 内容本身上设置阻断边界——阻断触发器或逻辑上的 React 父级是不够的posthog-instrumentation技能负责实现与浏览器探测工作流应与本审查配合使用。6.3 七项必需审查陈述数据策略明确普通 UI 哪些可保持可见、哪些客户可控值绝不能离开浏览器、哪个第三方接收数据、哪些部署/区域有资格。边界模糊的要升级给产品/法务批准追溯每个渲染器的数据来源包括输入与输出、数据集记录、提示词、生成内容、代码、标识符、名称、标签、评论、元数据、schema、选项、媒体/URL、以及复制进属性的值。覆盖只读、编辑、历史、diff、加载、空态、虚拟化、悬浮与对话框路径在共享边界强制执行阻断拥有敏感内容的最小共享渲染器。与其维护脆弱的逐页选择器列表不如让一个编辑器/查看器组件为所有调用方执行规则遵循 DOM 边界把自定义编辑器、语法高亮器、contenteditable 元素与 portal 内容当作独立于原生输入的面来对待。当属性或嵌套媒体可携带相同值时保护完整子树用显式资格做门控回放默认禁用。使用已批准托管部署类的允许列表排除合规区域与自托管/未知环境。遥测密钥、hostname 或远端 feature flag不能替代本地门控覆盖非 DOM 导出脱敏请求/响应体拒绝分析属性、console 捕获、自定义回放事件、错误 breadcrumb 与 URL 中的原始客户值测试负向契约为合规/受限/自托管部署添加配置测试为共享阻断边界添加组件测试并用唯一哨兵值做真实浏览器录制器探测。检查发出的事件证明禁用的文本与属性确实缺席。6.4 需要提出的发现回放默认激活或配置了遥测密钥即激活客户值在可编辑时受保护但在只读、预览、历史、diff、悬浮或生成输出形态下暴露被阻断的触发器打开了未阻断的 portal 内容保护覆盖了文本但同一个值仍留在title、aria-*、URL、网络请求体、console 条目或自定义事件中新敏感渲染器只依赖maskAllInputs、CSS 外观或页面级选择器测试只断言配置或 JSX 类名而没有在有效的录制器策略变更后检查有代表性的已发出回放。七、扩展这套技能与跨技能协作7.1 何时、如何新增主题参考每当某个安全发现跨功能或跨 PR 评审复现时就新增references/topic.md见 SKILL.md并保持每个参考文档窄而具体包含五个部分用平实语言描述威胁一段本仓库中的规范助手及路径可复制的已知正确调用点必需防御保存时、使用时、传输时等评审时要标记的反模式。然后在 checklist.md 中加一行指向新主题文件的触发点并在 SKILL.md 的表格中加一行。未来参考的候选在真实发现复现之前不要添加租户隔离Prisma 与 ClickHouse 上的projectId过滤、重定向误处理与敏感头传播、文件上传校验与 content-type 嗅探、新 tRPC / 公开 API 端点上的 RBAC scope 漂移、签名 URL 作用域过期、路径、方法、公开 API 限流与认证边界检查。7.2 与其他技能的分工共享code-review技能对任何命中上述触发器的改动应转介到这里见 code-review/SKILL.md 对应部分即 code-review/SKILL.md共享backend-dev-guidelines技能在添加出站 HTTP、集成配置或接受 URL 的过程时应转介到这里backend-dev-guidelines/SKILL.md有复现证据的确诊问题走linear-bug-triage交给 Linear 处理。八、自检要点与落地建议把整套技能浓缩为评审时的三个自问URL 面三层校验齐了吗保存时tRPC 变更/公开 API 路由 使用时/连接时worker 侧 dispatcher 钩子 重定向时fetchWithSecureRedirects且校验必须跑在服务端读取路径是允许列表还是删除列表一个共享转换器 穷尽never分发 投影兜底 VIEWER 负向测试拒绝任何as Safe*断言遥测边界是显式门控吗回放默认禁用、共享渲染器自带ph-no-capture、portal 内容独立阻断、负向契约有真实浏览器探测佐证。同时牢记两条工程纪律校验与引入面同批落地以后再做是设计缺陷以及负向测试缺失本身就是发现被阻止的输入——私有 IP、跨租户 ID、缺失 scope、Cloud 上的http:——必须被测试证明是拒绝的。这套技能的价值不在于知识本身而在于它把 Langfuse 团队用真实安全报告换来的教训固化成了每一个 Agent 在设计与评审时刻都会执行的标准动作。【免费下载链接】langfuse Open source AI engineering platform: LLM evals, observability, metrics, prompt management, playground, datasets. Integrates with OpenTelemetry, LangChain, OpenAI SDK, LiteLLM, and more. YC W23项目地址: https://gitcode.com/GitHub_Trending/la/langfuse创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考