Skip to content

chore(security): JSON-LD 序列化统一走 safeJsonLdString#338

Merged
longsizhuo merged 2 commits intomainfrom
security/p0-hotfix-2026-05-08
May 8, 2026
Merged

chore(security): JSON-LD 序列化统一走 safeJsonLdString#338
longsizhuo merged 2 commits intomainfrom
security/p0-hotfix-2026-05-08

Conversation

@longsizhuo
Copy link
Copy Markdown
Member

背景

2026-05-07 三方代码 review(backend / frontend / ChatBot)发现一条串成一条的 P0 攻击链。前端这一环是个人主页 JSON-LD 把用户 bio 直接 JSON.stringify 嵌进 <script type=\"application/ld+json\">——JSON.stringify 默认不转义 <,攻击者填一段 </script><script>...</script> 就触发存储型 XSS,配合 satoken 存 localStorage 等于完整账户接管。

后端侧的修复见 InvolutionHell/involutionhell-backend#26(INV-001 ~ INV-005)。本 PR 处理前端这一环:INV-FE-001。

改动

新增 lib/json-ld.ts

export function safeJsonLdString(payload: unknown): string {
  return JSON.stringify(payload)
    .replace(/</g, \"\\\\u003c\")
    .replace(/>/g, \"\\\\u003e\")
    .replace(/&/g, \"\\\\u0026\")
    .replace(/\\u2028/g, \"\\\\u2028\")
    .replace(/\\u2029/g, \"\\\\u2029\");
}

把会闭合 script 块的字符替换成字面 6 字符 \uXXXX 序列。JSON.parse 仍能正确还原;浏览器 HTML 解析器看不到 < 不会闭合 script。

迁移点

文件 字段
app/[locale]/u/[username]/page.tsx personJsonLd(含用户 bio,攻击面最大
app/[locale]/docs/[...slug]/page.tsx articleJsonLd / breadcrumbJsonLd(slug 经 decodeURIComponent)
app/layout.tsx WebSite / Organization 结构化数据(常量但 defense-in-depth)

文档

docs/SECURITY_INVARIANTS.md 登记 INV-FE-001(与公开的 frontend/SECURITY.md 漏洞披露政策不冲突,是给维护者看的内部不变量清单)。

后端 SECURITY.md 用 INV-001/002/...,前端用 INV-FE-001/... 编号空间互不重叠。

测试

pnpm vitest run tests/json-ld.test.ts

4 条覆盖:

  • 攻击载荷 </script><script>...</script> 不再出现在输出里
  • 普通对象仍是合法 JSON(JSON.parse 能还原)
  • < > & 都被转义为 \\u003c
  • user-generated 字段含恶意标签往返保真

pre-commit hook 全套 23 测试通过、prettier 格式化、lockfile / pnpm 版本校验通过。

Test plan

  • pnpm vitest run tests/json-ld.test.ts 4 / 4
  • pnpm tsc --noEmit 通过
  • Reviewer:构建 dev server,把测试账号 bio 改成 </script><script>alert(1)</script>,访问 profile 页确认 alert 不弹(输出里看到的是字面 \u003c 转义)
  • Reviewer:检查 docs slug 页 / 首页 view-source,确认 JSON-LD 块仍是合法 JSON(结构化数据测试工具能解析)

新增 lib/json-ld.ts 把 JSON.stringify 输出里能闭合 <script> 块的字符
(< > & U+2028 U+2029)替换成字面 \uXXXX 6 字符序列。JSON.parse 仍能
还原;浏览器 HTML 解析器看不到 < 自然不会闭合 script 块,阻断 stored XSS。

迁移点:
- app/[locale]/u/[username]/page.tsx 的 personJsonLd(含用户 bio,攻击面最大)
- app/[locale]/docs/[...slug]/page.tsx 的 articleJsonLd / breadcrumbJsonLd
- app/layout.tsx 的 WebSite / Organization 结构化数据(当前都是常量但
  defense-in-depth 一并迁移,避免未来加 user-generated 字段时漏改)

测试:tests/json-ld.test.ts 4 条覆盖
- </script> 攻击载荷不再出现在输出里
- 普通对象仍是合法 JSON(JSON.parse 能还原)
- < > & 都被转义
- user-generated 字段往返保真

文档:docs/SECURITY_INVARIANTS.md 新增 INV-FE-001 条目,与 backend 仓库
SECURITY.md 共用同一套不变量编号空间(前端用 INV-FE-* 前缀避免冲突)。

历史:2026-05-07 三方 CR attack chain A 起点。配合 backend 仓库
PR #26 一同收口(admin→superadmin 提权阻断 / chat 越权写阻断 / 密码迁
bcrypt / user_follows 表补建 / compose 弱密码默认值收紧)。
Copilot AI review requested due to automatic review settings May 8, 2026 01:55
@vercel
Copy link
Copy Markdown

vercel Bot commented May 8, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
involutionhell-github-io Ready Ready Preview, Comment May 8, 2026 2:30am
website-preview Ready Ready Preview, Comment May 8, 2026 2:30am

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

本 PR 为前端 JSON-LD 注入点提供统一的安全序列化方法,修复将用户可控字段直接 JSON.stringify 嵌入 <script type="application/ld+json"> 导致的存储型 XSS 风险(INV-FE-001),并在关键页面完成迁移与回归测试。

Changes:

  • 新增 safeJsonLdString:对 JSON.stringify 输出进行必要字符转义,确保可安全嵌入 JSON-LD <script>
  • 将 3 处 JSON-LD dangerouslySetInnerHTMLJSON.stringify 迁移为 safeJsonLdString
  • 新增回归测试与维护者安全不变量文档(SECURITY_INVARIANTS)

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
lib/json-ld.ts 新增 JSON-LD 安全序列化工具函数 safeJsonLdString
app/layout.tsx 站点级 WebSite / Organization JSON-LD 改用 safeJsonLdString
app/[locale]/u/[username]/page.tsx 个人主页 Person JSON-LD(含 bio)改用 safeJsonLdString
app/[locale]/docs/[...slug]/page.tsx Docs 的 Article/Breadcrumb JSON-LD 改用 safeJsonLdString
tests/json-ld.test.ts 增加 INV-FE-001 回归测试覆盖 XSS payload 与往返保真
docs/SECURITY_INVARIANTS.md 登记前端安全不变量 INV-FE-001 与检查/测试策略

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/json-ld.ts Outdated
Comment thread lib/json-ld.ts Outdated
Comment thread docs/SECURITY_INVARIANTS.md Outdated
Comment thread docs/SECURITY_INVARIANTS.md Outdated
Comment thread tests/json-ld.test.ts Outdated
Comment thread tests/json-ld.test.ts Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@longsizhuo longsizhuo merged commit 1cf69b1 into main May 8, 2026
6 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants