十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Effect HttpApi 文档页面 HTML 渲染安全加固:Scalar 与 Swagger 的转义与脚本逃逸防护

Effect HttpApi 文档页面 HTML 渲染安全加固:Scalar 与 Swagger 的转义与脚本逃逸防护 Effect HttpApi 文档页面 HTML 渲染安全加固Scalar 与 Swagger 的转义与脚本逃逸防护【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code导读Effect 的HttpApi模块允许开发者用纯声明式的方式描述 REST API 契约并自动生成 OpenAPI 规范与交互式文档页面Scalar 与 Swagger UI。本文围绕 effect-smol 仓库中harden-httpapi-documentation-html这一变更见 changeset 原文深入剖析文档 HTML 渲染中曾存在的两处安全缺陷——属性值/CDN 版本号未按上下文转义、内嵌 JSON 仅处理精确/script序列——并讲解修复后的源码级实现与测试验证。读完本文你将理解为什么看似简单的字符串拼接在生成 HTML 文档页面时容易引入脚本注入风险以及如何通过分层转义策略彻底封堵。背景HttpApi 如何零成本生成文档页面在 Effect 的unstable/httpapi命名空间中一个HttpApi通过链式调用即可声明资源、分组、端点与 OpenAPI 元数据。文档 UI 则作为可挂载到HttpRouter上的 Layer 提供HttpApiScalar.ts挂载基于 Scalar 的 API 参考页面HttpApiSwagger.ts挂载 Swagger UI 页面两者都通过OpenApi.fromApi(api)在运行时从契约生成 OpenAPI 文档因此无需手写或单独存放 OpenAPI 文件即可在GET /docs默认路径暴露交互式文档。生成 HTML 的核心逻辑位于各自模块内部的makeHandler中并且使用了Function.memoize对响应做记忆化缓存——同一 API 的文档页只在首次请求时生成一次。这一设计也直接决定了任何注入成功都会长期驻留在缓存的 HTML 响应里因此渲染环节的转义正确性比普通页面更加关键。缺陷分析两处会被浏览器另作解读的拼接点原 changeset 明确指出加固前的两个问题Scalar 的 description 元数据与 CDN 版本号在被拼入 HTML 时没有做面向属性上下文的转义attribute-safe escaping。这意味着 API 契约中的title、description等字段如果包含、、、可能逃逸出原本的标签或属性边界注入新的标签乃至脚本。内嵌在script元素中的 OpenAPI JSONScalar 的content与 Swagger 的swagger-spec只处理了精确的/script序列而没有覆盖 HTML 规范中 script data end tag name state 允许的其它合法结束标签形式。第 2 点尤其隐蔽。按 HTML 解析规范script元素内容中的结束标签允许在/script之后出现空白字符如/script 、/如/script/等多种形态。攻击者只需让 OpenAPI 文档的某个字符串字段携带/script 或/script/等变体就能让浏览器提前终止当前 script 元素随后把攻击者构造的后续内容当作新的 HTML/脚本解析执行——即存储型 XSS。仓库测试用例中的注入载荷/script ${injectedTag}与/script/${injectedTag}正是对这一场景的复现。修复实现internal/html.ts 的分层转义原语加固的核心是 internal/html.ts 中三个按输出上下文区分的转义函数const ESCAPE_SCRIPT_DATA //g const ESCAPE_LINE_TERMS /[\u2028\u2029]/g /** JSON 内嵌到 script 中使用转义 与行分隔符 */ export function escapeJson(spec: unknown): string { return JSON.stringify(spec) .replace(ESCAPE_SCRIPT_DATA, \\u003c) .replace(ESCAPE_LINE_TERMS, (c) c \u2028 ? \\u2028 : \\u2029) } /** HTML 文本内容标签体使用 */ export function escape(str: string): string { return str .replace(//g, amp;) .replace(//g, lt;) .replace(//g, gt;) } /** 属性值上下文使用在 escape 基础上再转义引号 */ export function escapeAttribute(str: string): string { return escape(str) .replace(//g, quot;) .replace(//g, #39;) }三种原语各司其职对应三种不同的 HTML 输出位置原语适用上下文关键转义escape标签内文本如titleescapeAttribute属性值如content...在escape基础上追加、escapeJsonscript内的 JSON 数据JSON 字符串中的→\u003c并处理 U2028/U2029为什么escapeJson转义而非/script把序列化的 JSON 里每一个都替换成\u003cJSON 合法的 Unicode 转义等价于保证 JSON 文本中不可能出现任何以起始的字节序列——自然也就不可能存在/script、/script 、/script/乃至任何未来的 script 结束标签变体。这是一种按字符集根除比维护一份已知结束标签黑名单更彻底也解释了 changeset 中embedded JSON escapesso it cannot close its script element的表述。此外U2028行分隔符与 U2029段分隔符在 JavaScript 字符串字面量中属于非法字符统一转义为\u2028/\u2029可避免 JSON 被内联进 script 时触发解析错误。加固后的文档页面渲染流程ScalarCDN 版本与元数据的双重防护HttpApiScalar.ts 中的makeHandler是 Scalar 页面渲染的入口L143-L199。加固后每一个插值点都选择了正确的转义原语const scalarScript options.source._tag Cdn ? script src${ Html.escapeAttribute( https://cdn.jsdelivr.net/npm/scalar/api-reference${ encodeURIComponent(options.source.version ?? latest) }/dist/browser/standalone.min.js ) } crossorigin/script : script${options.source.source}/scriptCDN 版本号先经encodeURIComponent做 URL 编码再用Html.escapeAttribute转义属性上下文即使版本号携带、等字符也无法逃出src...属性。title与 meta 描述spec.info.title走Html.escapedescription同时写入meta namedescription与meta nameog:description两处均使用Html.escapeAttribute。Scalar 配置与 OpenAPI 文档scalarConfig与spec分别经Html.escapeJson序列化后拼入window.Scalar.createApiReference(...)调用L186-L194。可挂载的 Layer 有两个变体layer内联打包 Scalar 脚本见 L212-L229与layerCdn从 jsDelivr 加载可通过version指定 Scalar 版本见 L243-L261默认路由均为GET /docs。Swaggerscript 数据块的整体转义HttpApiSwagger.ts 的模板L27-L50将 OpenAPI 文档写入一个专门的 JSON script 数据块再由页面脚本读取script idswagger-spec typeapplication/json ${Html.escapeJson(spec)} /script script window.onload () { window.ui SwaggerUIBundle({ spec: JSON.parse(document.getElementById(swagger-spec).textContent), dom_id: #swagger-ui, }); }; /scriptescapeJson的应用保证了数据块内不可能出现任何 script 结束标签变体同时由于 JSON 转义后可被JSON.parse无损还原文档数据完整性不受影响。测试验证用真实注入载荷检验转义加固行为在 HttpApiDocumentation.test.ts 中有对应的回归测试测试通过HttpRouter.toWebHandler起真实 handler 并抓取GET /docs的 HTML 文本进行断言Scalar 元数据转义测试L183-L201构造标题Docs title /titlescript与描述quoted single /script script idscript-data-injected断言渲染结果中标题被转义为lt;/titlegt;lt;scriptgt;、描述被转义为quot;quotedquot; #39;single#39; lt;/script gt;...并确认 HTML 中不再出现原始/script 同时用extractSpec反序列化验证 OpenAPI 数据仍能完整还原。CDN 版本编码测试L203-L215注入版本号1.2.3/scriptscript idinjected断言最终src属性是encodeURIComponent后的安全 URL、页面中script标签总数仍为 2且注入的idinjected不存在。Swagger script 结束标签变体测试L287-L297注入描述/script/${injectedTag}断言渲染结果中不包含原始/script/且extractSwaggerSpec能无损解析出原 OpenAPI 文档。这些测试同时覆盖了 changeset 提到的两类攻击面属性上下文注入与 script 结束标签变体逃逸并且都包含转义后数据可还原的正向断言防止过度转义破坏文档功能。实战在应用中启用安全的文档路由在应用中使用上述 Layer 的方式如下路由默认均为/docs可通过path参数修改import { HttpApi, HttpApiBuilder, HttpApiScalar, HttpApiSwagger } from effect/unstable/httpapi // 声明 API 契约可附加 OpenAPI 元数据title / description 等 const Api HttpApi.make(Docs) .add(MyGroup) .annotate(OpenApi.Title, My API) .annotate(OpenApi.Description, API description) // 方式一Scalar内联脚本 HttpApiBuilder.group(Api, docs, (builder) builder.add(HttpApiScalar.layer(Api))) // 方式二ScalarCDN 加载可固定版本 HttpApiBuilder.group(Api, docs-cdn, (builder) builder.add(HttpApiScalar.layerCdn(Api, { path: /docs, scalar: { theme: purple, darkMode: true }, version: 1.27.0 }))) // 方式三Swagger UI HttpApiBuilder.group(Api, swagger, (builder) builder.add(HttpApiSwagger.layer(Api)))ScalarConfig支持的主题预设ScalarThemeId包括alternate、default、moon、purple、solarized、bluePlanet、saturn、kepler、mars、deepSpace、laserwave、none布局可选modern或classic另有proxyUrl、customFetch、hideModels、hideTestRequestButton、withDefaultFonts、showOperationId等展示与功能开关见 HttpApiScalar.ts L28-L131。需要提醒的是customFetch本身是浏览器端 JavaScript 函数表达式属于有意执行的代码通道使用时务必只传入可信的静态内容切勿与不可信输入拼接。总结文档页面生成的安全要点本次加固为同类运行时生成 HTML 文档场景提供了三条可复用的经验按输出上下文选择转义函数文本节点、属性值、script 内嵌数据三者边界规则不同绝不能用同一个转义函数一把梭对 script 数据块采取字符级根除转义 JSON 中的而非维护结束标签黑名单可一劳永逸地覆盖 HTML 规范允许的所有 script 结束标签形态含空白、斜杠等变体对用户可控的版本号/URL 片段先做对应协议的编码如encodeURIComponent再做 HTML 属性转义形成纵深防御。从 changeset 到 转义原语、Scalar 渲染、Swagger 渲染 与回归测试可以看到 Effect 团队以最小变更 针对性测试的方式完整闭环了这次安全加固——这也是在任意语言与框架中处理 HTML 输出时都值得遵循的严谨路径。【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表