Conversation
使用 DOMPurify 对服务端下发的公告 HTML 进行白名单过滤,防止潜在的 UI 注入风险。只允许基础标签和安全的 CSS 属性(颜色、字体相关)。 Co-Authored-By: Claude Opus 4.6 <[email protected]>
There was a problem hiding this comment.
Pull request overview
该 PR 针对弹窗里“更新公告/通知”由服务端下发并以 dangerouslySetInnerHTML 渲染的场景,引入 DOMPurify 白名单清理,降低扩展 UI 被远程 HTML 注入的安全风险(对应 issue #1273)。
Changes:
- 新增
src/pkg/utils/sanitize.ts:封装基于 DOMPurify 的 HTML 清理与 style 属性白名单过滤 - Popup 公告渲染前对
checkUpdate.notice做清理后再注入 - 增加
dompurify依赖并更新锁文件
Reviewed changes
Copilot reviewed 3 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/pkg/utils/sanitize.ts | 新增 DOMPurify 清理逻辑与 CSS 白名单 hook |
| src/pages/popup/App.tsx | 公告内容渲染前调用 sanitizeHTML |
| package.json | 添加 dompurify 运行时依赖 |
| pnpm-lock.yaml | 锁定新增依赖及其可选依赖 |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
| // 过滤不安全的 CSS 属性,只保留白名单中的属性 | ||
| DOMPurify.addHook("afterSanitizeAttributes", (node) => { | ||
| if (node instanceof HTMLElement && node.hasAttribute("style")) { | ||
| const { style } = node; | ||
| for (let i = style.length - 1; i >= 0; i--) { | ||
| if (!ALLOWED_CSS_PROPERTIES.includes(style[i])) { | ||
| style.removeProperty(style[i]); | ||
| } | ||
| } | ||
| } | ||
| }); |
There was a problem hiding this comment.
这里在模块顶层调用 DOMPurify.addHook 会产生全局副作用:任何后续对 DOMPurify 的使用都会被这个 style 白名单逻辑影响,并且在热重载/多实例场景可能重复注册 hook。更稳妥的做法是创建专用的 DOMPurify 实例(例如基于当前 window 创建实例)并只在该实例上注册 hook,或至少加一次性初始化保护。
| // 对 HTML 进行清理,只保留安全的标签和属性 | ||
| export function sanitizeHTML(html: string): string { | ||
| return DOMPurify.sanitize(html, { | ||
| ALLOWED_TAGS: ["b", "i", "a", "br", "p", "strong", "em", "span"], | ||
| ALLOWED_ATTR: ["href", "target", "style"], | ||
| }); | ||
| } |
There was a problem hiding this comment.
新增的 sanitizeHTML 属于安全关键逻辑,但目前在 src/pkg/utils/ 下没有对应单测覆盖。建议补充 sanitize.test.ts:至少覆盖(1)移除 <script>/事件处理属性(2)过滤非白名单标签与属性(3)style 仅保留白名单 CSS 属性,且能正确剔除如 position/background/top 等。
src/pages/popup/App.tsx
Outdated
| style={{ display: showAlert ? "flex" : "none" }} | ||
| type="info" | ||
| content={<div dangerouslySetInnerHTML={{ __html: checkUpdate.notice || "" }} />} | ||
| content={<div dangerouslySetInnerHTML={{ __html: sanitizeHTML(checkUpdate.notice || "") }} />} |
There was a problem hiding this comment.
这里在组件每次 render 时都会调用一次 DOMPurify 清理,DOMPurify 相对开销较大,可能导致 popup 频繁重渲染时的性能抖动。建议用 useMemo 基于 checkUpdate.notice 缓存清理后的字符串(或在拿到 notice 时就预先清理并存储)。
| return DOMPurify.sanitize(html, { | ||
| ALLOWED_TAGS: ["b", "i", "a", "br", "p", "strong", "em", "span"], | ||
| ALLOWED_ATTR: ["href", "target", "style"], | ||
| }); |
There was a problem hiding this comment.
sanitizeHTML 允许 target 但未同时允许/补齐 rel="noopener noreferrer"。当公告里存在 <a target="_blank"> 时可能引入反向 tabnabbing 风险(新页面可通过 window.opener 操作原扩展页面)。建议:要么不允许 target,由外层统一用安全方式打开链接;要么在 DOMPurify hook 中对 A 标签设置 rel=noopener noreferrer(并将 rel 加入允许属性)。
cyfung1031
left a comment
There was a problem hiding this comment.
@CodFrm 你直接改 JSON 吧。内容在ScriptCat扩充做Render
资讯下载量也会较小
这些 DOMPurify不太可靠
什么意思?改JSON? |
打錯字。改用JSON 由「接收服务端下发的公告 HTML 」改成 「接收服务端下发的公告 JSON 」 而不是直接下载 HTML内容 直接放在 React |
呀。现在已经是 json 把这个 notice 做成物件吧 例如 |
|
或者你想简单点 可以用这个 https://github.com/lemonadejs/html-to-json 把 type 不合适的都删掉,只保留 div, span, b, p, br 等 |
呀 这个 DOMPurify 看来也不错 |
|
你改到sw环境去了,好像出问题了 |
呀。它可能要用 document 来做处理。。。 sw 不能用sanitizeHTML |
* 🔒 使用 DOMPurify 清理公告通知的 HTML 内容 #1273 使用 DOMPurify 对服务端下发的公告 HTML 进行白名单过滤,防止潜在的 UI 注入风险。只允许基础标签和安全的 CSS 属性(颜色、字体相关)。 * code update --------- Co-authored-by: cyfung1031 <[email protected]>
* additional test for responseType=document * 🐛 修复 脚本设置-授权管理 控制无效的问题 (#1267) * 🐛 修复 脚本设置-授权管理 控制无效的问题 * 将校验逻辑放到confirm * 修复GM cookie权限判断 * 删除directDeny * buildCacheKey * 整理代码 * Update src/app/service/service_worker/permission_verify.ts Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Copilot <[email protected]> * 🔒 使用 DOMPurify 清理公告通知 HTML 内容 (#1274) * 🔒 使用 DOMPurify 清理公告通知的 HTML 内容 #1273 使用 DOMPurify 对服务端下发的公告 HTML 进行白名单过滤,防止潜在的 UI 注入风险。只允许基础标签和安全的 CSS 属性(颜色、字体相关)。 * code update --------- Co-authored-by: cyfung1031 <[email protected]> * 🐛 修复 include *?* 表达式处理问题 #1271 (#1272) * 🐛 修复 include *?* 表达式处理问题 #1271 * Added error handling to avoid crash * update globSplit * update globSplit * update globSplit --------- Co-authored-by: cyfung1031 <[email protected]> * fix #1274 (#1275) * fix #1274 * Update index.ts * Array.includes -> Set.has * 🐛 修复与隐身窗口检查权限冲突导致反复重启的问题 * 📄 docs: update Chrome Web Store URLs to new domain (#1279) Google migrated the Chrome Web Store from `chrome.google.com/webstore` to `chromewebstore.google.com`. This updates all CWS URLs across README files to use the new domain. The old URLs currently redirect, but will eventually stop working. * ✅ 添加 Playwright E2E 测试及 GM API 功能测试 (#1283) * ✅ 添加 Playwright E2E 测试 - 新增 22 个 E2E 测试覆盖 Options、Popup、Install、Editor、Settings 页面 - 配置 Playwright 使用 --headless=new 模式加载扩展 - 在 CI workflow 中添加 E2E 测试 job * ✅ 添加 GM API E2E 测试 新增 gm-api.spec.ts 测试三类 GM API: - GM_ 同步 API (gm_api_test.js): 29 项测试 - GM.* 异步 API (gm_api_async_test.js): 29 项测试 - Content 注入测试 (inject_content_test.js): 11 项测试 实现要点: - 两阶段浏览器启动:Phase 1 启用 userScriptsAccess,Phase 2 重启运行测试 - 自动审批权限确认弹窗(cookie 等需要用户授权的 API) - 通过剪贴板注入脚本代码到 Monaco 编辑器 - 替换 jsdelivr CDN 为 unpkg 提升资源加载速度 - 去除 @require/@resource 的 SRI hash 避免校验失败 更新 utils.ts 中 installScriptByCode 增加保存失败的 fallback 检测 * 🐛 修复 GM API E2E 测试 CI 兼容性 - Phase 1 添加 --headless=new 参数,修复 CI 无 X server 环境 - 添加 eslint-disable 注释消除 Playwright use() 的误报 - prettier 格式化修正 * 🐛 修复 E2E 测试 CI 兼容性问题 - vitest.config.ts: 排除 e2e/ 目录避免 Vitest 误跑 Playwright 测试 - eslint.config.mjs: 为 e2e/ 目录关闭 react-hooks/rules-of-hooks 规则 - e2e/options.spec.ts: 菜单正则加 /i 标志修复英文环境大小写匹配 - prettier 格式化修正 * 🚑 修复其他扩展注入 chrome.runtime 导致环境误判的问题 #1280 (#1281) * 🐛 修复其他扩展注入 chrome.runtime 导致环境误判的问题 #1280 移除 isContent 运行时检测,改为通过 CustomEventMessage.envTag 在构建入口确定环境, 避免其他扩展(如大学搜题酱)向页面注入 chrome.runtime 对象导致 inject 环境被误判为 content 环境。 * Update src/app/service/content/gm_api/gm_api.ts Co-authored-by: Copilot <[email protected]> * update * 删除不必要的isconnect * 将 typecheck 集成到 lint/lint-fix 脚本中 * 修复引用 * 删除test-results --------- Co-authored-by: cyfung1031 <[email protected]> Co-authored-by: Copilot <[email protected]> * ⚙️ 优化 CI 流水线和测试配置 - 缓存 Playwright 浏览器避免重复下载 - 测试失败时上传截图/视频/报告等调试产物 - Playwright CI 环境启用 HTML+list 双 reporter、失败截图和视频 - 各工具链配置屏蔽 .claude 目录 * ✅ 修复 e2e 测试 service worker 超时并优化等待策略 - gm-api.spec.ts: Phase 2 重启 context 后等待 service worker 注册完成 再交给 fixtures,避免 extensionId fixture 用 10s 全局超时等待失败 - gm-api.spec.ts: 用事件驱动 Promise 替换 500ms 轮询循环, console 结果一出现立即继续 - utils.ts: installScriptByCode 用 DOM 事件等待替代固定延迟: click 后等光标出现,粘贴后等 .view-lines 内容变化 --------- Co-authored-by: wangyizhi <[email protected]> Co-authored-by: Copilot <[email protected]> Co-authored-by: Michael Lip <[email protected]>
概述 Descriptions
close #1273
变更内容 Changes
src/pkg/utils/sanitize.ts封装清理逻辑b,i,a,br,p,strong,em,spancolor,font-size,font-weight,font-styleposition、background)会被自动过滤src/pkg/utils/sanitize.tsto encapsulate sanitization logicb,i,a,br,p,strong,em,spancolor,font-size,font-weight,font-styleposition,background) are automatically stripped截图 Screenshots
N/A