基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
AIHT
内容
精选全部 AI 动态热点榜AI 日报收藏
分类
模型行业编程产品技术观点开源
更多
Agent 接入
返回首页开发者导航
AIHT
精选 / 资讯详情
8.0
热点
AI SCORE
编程提效2026-10-23 07:11

测试全绿的扩展,仍被 AI 找出安全漏洞

dev.to · AI#代码审查#安全#测试
Editor brief · 编辑速览

作者的 Raycast 发票预览扩展通过了单元测试和类型检查,自动审查却提出四项安全问题。已展示的案例涉及未经运行时校验的 JSON 字段进入 HTML,以及 Markdown 注入形成追踪像素。

文章思维导图
Knowledge map
拖拽缩放
AI deep read

AI 深度解读

一个粘贴发票 JSON、生成 HTML 和 Markdown 预览的 Raycast 扩展,渲染逻辑零依赖,7 个单元测试通过,tsc 检查也没有报错。提交审核后,自动审查工具 Greptile 几分钟内留下 9 条评论,其中 4 条被标为 P1/security。

这个案例值得读,不是因为 AI 找到了多复杂的漏洞,而是它揭开了一个常见盲区:测试覆盖了开发者想象中的输入,却没有检查输入越过信任边界后会发生什么。类型、文本和文件名,恰好都可能在这些边界上失效。

TypeScript 的 number,挡不住 JSON 里的字符串

扩展从用户粘贴的内容中调用 JSON.parse 获取数据。作者把 tax_rate 声明为 number,随后便按数字使用,并让它未经转义进入 HTML 模板。

但用户可以提交这样的字段:"tax_rate": "0\"><script>alert(1)</script>"。类型声明不会在运行时把这段字符串拦下来。作者转义了自己认定为字符串的字段,却信任了这个看起来应该是数字的字段。

类型检查通过,不代表外部输入已经通过校验。

同样的误判也出现在数组上。作者只检查了 Array.isArray(data.items) && data.items.length > 0,下面的数据就能通过:

{"items": [null]}

这里的关键是 null:容器确实是非空数组,但读取元素的 .qty 时,computeTotals 仍然会崩溃,整个预览随之失败。

作者增加了 sanitizeItems(),在数据进入渲染器前统一做类型转换和过滤。这个方向成立,不过原文没有给出完整修复代码,尤其没有说明 tax_rate 最终如何校验,也没有交代非法行被过滤后如何提示用户,不能仅凭函数名判断所有边界都已覆盖。

同一个「文本字段」,进入不同渲染器就有不同风险

第二个问题出在 notes 字段。它被直接送入 Raycast Detail 的 Markdown 预览,作者虽然处理了 HTML 特殊字符,却没有处理 Markdown 语法。

当备注包含指向远程地址的 Markdown 图片标记时,预览会把它识别为图片,并向指定地址发起 HTTP 请求。这里不需要执行脚本,单是加载远程资源,就能成为跟踪像素。

HTML 转义解决不了这个问题,因为 Markdown 有自己的特殊字符集合:反引号、* _ { } [ ] ( ) # + . ! | < > -。作者因此增加了与 HTML 转义函数并列的 escapeMarkdown()。

对开发者来说,关键不是记住一张字符表,而是先确定字段允许表达什么。如果备注只应显示普通文字,就要阻止它变成图片、链接等可执行的渲染指令;如果产品允许 Markdown,则需要进一步明确允许哪些语法。原文没有讨论后一种设计。

文件名过滤了,为什么还会丢数据

保存逻辑看起来已经做了防护:把发票编号中的特殊字符替换成下划线,再拼成文件路径。

// before
const file = path.join(dir, `${(data.invoice_number || "invoice").replace(/[^a-z0-9-_]/gi, "_")}.html`);
fs.writeFileSync(file, html, "utf8");

要注意第一行的 .replace()。INV/001 和 INV:001 都会变成 INV_001.html,第二次保存便会静默覆盖第一次的文件。

字符过滤只规定了名字可以包含什么,没有保证不同发票拥有不同名字。

作者在 writeFileSync 前增加了重名检查,并用 try/catch 捕获保存失败,通过 toast 提示用户。四个问题的修复合计大约 40 行。改动不大,漏掉的却涉及脚本注入、远程请求、预览崩溃和文件覆盖,后果并不相同。

审核还在第二轮发现了回归测试的问题:针对空元素添加的测试,把 @ts-expect-error 放错了位置,导致类型检查在本该失败时通过。原文没有提供具体测试代码,无法进一步判断误放位置如何影响检查,但它提醒了另一件事:新增测试也需要审核,绿灯本身不是充分证据。

我的判断

作者最有说服力的地方,是提供了能直接复现的输入。恶意字符串、[null] 和两个冲突编号,都能明确指向代码中的信任假设,比泛泛建议「加强安全意识」有用。

但这仍是一次个人项目的审查记录。Raycast 对进入仓库的 PR 运行 Greptile,这次得到 9 条评论、4 条 P1/security,不足以证明 AI 审查优于人工审查,也不能推导出它的误报率或漏洞检出率。原文没有对照数据,这部分只能视为经验。

我认同把 AI 审查加入流程,但更看重它能否把问题转成可复现用例。运行时校验、针对输出格式的处理、回归测试和人工复核,仍然各有职责。尤其涉及金额的数据,「转换并过滤」还需要解释清楚:错误数据是拒绝、修正还是忽略,不能只以页面不崩溃为验收标准。

能拿来做什么

  • 在处理粘贴 JSON、导入文件或接口响应时,把运行时校验放在渲染和计算之前。除了检查数组,还要检查元素是否为空、字段类型是否符合要求。
  • 检查前端富文本预览或 Markdown 展示中的普通文本字段,确认它们是否意外支持远程图片;分别验证 HTML 和 Markdown 输出,避免共用一种转义就算完成。
  • 给 Node 文件导出补上名称冲突用例,直接用 INV/001、INV:001 验证保存行为,并检查写入失败时用户能否看到明确反馈。
  • 让 AI 编程助手专门寻找能破坏现有假设的输入,并要求给出复现步骤。采纳修复后,同时审核回归测试和 @ts-expect-error 的位置。
Original source

本文由 AI 通读原文后重新编写,观点部分为站内判断,细节以原文为准,来源 dev.to · AI,原文版权归原作者所有。

阅读原文
上一篇
Webhook 验签故障排查与防重放实现
下一篇
用开源 MCP 接入 Titan 企业邮箱

资讯速览

dev.to · AI · 2026-10-23 07:11

AI 评分 8.0 / 10

相关标签

#代码审查#安全#测试

继续阅读

  • 用 Dynamo 部署预填充与解码分离推理
  • Webhook 验签故障排查与防重放实现
  • 用开源 MCP 接入 Titan 企业邮箱
  • 两份文本搭建最小 Agent 评测
  • 让每个 PR 自动完成测试与修复闭环