托管自执行JavaScript的HTML安全性引发讨论
速览
该标题提出一个技术安全问题:是否安全地托管一段包含自身JavaScript代码的HTML。这涉及跨站脚本攻击、沙箱隔离、内容安全策略等议题。对于开发者而言,理解此类托管的风险有助于防范潜在漏洞。尽管并非AI相关,但属于前端安全领域的常见话题。
AI 深度解读
背景
2026年7月8日,Hacker News上的一篇技术文章引发讨论:当用户上传的HTML文件中包含任意JavaScript代码时,托管方如何保证安全?ShareMyPage 是一个允许用户分享任意HTML页面的产品,其中大量HTML由AI生成,未经过逐行审查。面对“如何防止注入或会话劫持”的尖锐问题,团队给出了一个反直觉的答案:不清理HTML,而是假设每个页面都是恶意的,并让这个假设变得无关紧要。
核心内容
ShareMyPage 刻意不做任何HTML消毒(sanitize),原因有二:一是消毒会破坏产品核心功能——用户上传的是真正的页面,包含动态图表、可交互原型、小型工具,JavaScript 是灵魂,去掉就只剩截图;二是消毒不信任的HTML是一场永无休止的军备竞赛,防御方只要漏掉一个漏洞就会全线失守。因此团队采取了相反策略:原样运行页面,但把它放在一个无法对你、对我们、对任何打开它的人造成伤害的地方。
整套方案的核心是:不试图让HTML变得安全,而是让运行它的环境变得安全。 文件本身完全不需要被信任。
每个页面运行在无源(origin)的沙箱中
共享页面绝不在主站直接渲染,而是通过一个沙箱化的 iframe 加载。沙箱只授予页面成为“真实页面”所必需的最小权限,而剥夺所有能触及外部的能力。
沙箱中缺少的权限列表比存在的更重要:没有 allow-same-origin。这个单一缺失让被嵌入的文档获得一个不透明的 null origin。其中的脚本仍然可以运行(图表动画、表单提交等),但 document.cookie 返回空,localStorage 不可用,也无法读取或访问嵌入它的父页面。这是一个不属于任何源的活页面。
最后两个条目 allow-popups-to-escape-sandbox 和 allow-top-navigation-by-user-activation 是另一种权限:它们允许用户的点击触达外部世界,比如“预约通话”链接或 mailto 链接可以正常打开,而不是静默失败。这些权限仅限真实用户手势触发,且只控制导航,从不涉及数据——上述关于 cookie、存储和源的保护完全不受影响。
一个永远不打破的规则: allow-scripts 和 allow-same-origin 绝不能同时出现。一旦组合,被嵌入的脚本就会重新获得真实源,悄悄撤销所有保护。保持这两者分离是承重线。
页面托管在独立的、无 cookie 的源上
沙箱是第一层,第二层是内容从何处提供。HTML 并非来自你登录的应用源,而是从另一个不同的源通过一个短期签名的 URL 提供。该源从不设置或读取 cookie。你的登录会话位于应用源上,在内容源上根本不存在。因此,即使理论上某个脚本突破了沙箱,在那个源上也无物可偷:没有会话 cookie、没有 token、没有任何登录状态。团队代码中的注释写得很直白:即使沙箱逃逸,也找不到任何可窃取的东西。
此外,团队还锁定了只有自己的应用才能嵌入这些内容,防止页面被拖走并用于在其他地方伪装成可信框架。
直接回答注入和会话劫持
注入(通常是跨站脚本 XSS) 是未信任的 HTML 在你信任的、带有 cookie 的源中执行,从而可以读取属于你的 DOM 和 cookie。在 ShareMyPage 上,未信任的 HTML 从未接触那个源——没有可信上下文供它注入。它被架构本身隔离,而不是靠检查。
JavaScript 会话劫持 通过读取会话 cookie 或 token 并重放来实现。在沙箱中,脚本拥有 null origin,所以 cookie 和存储访问在启动前就消失了;而且它运行的源本身就不携带任何会话。两个独立的原因让攻击无处下手。
多层防御,没有单一锁点
好的安全架构不依赖一个巧妙的控制,而是叠加多个独立层,任何单层失效时其余层依然有效:
- 页面运行在 null-origin 沙箱中,脚本得不到 cookie、存储,也无法触及父页面。
- 页面从独立的、无 cookie 的源提供,所以那里本来就没有会话可攻击。
- 页面通过短期签名 URL 加载,不是可猜测的路径,且嵌入仅限自家应用。
- 你登录的应用本身有严格的内容安全策略,外壳本身无法被嵌入或注入。
- 每次读取都在服务端鉴权,并限定在你的工作区,因此即使链接泄露也无法打开你无权查看的页面。
- 页面以私有加密文件形式存储在欧盟,从不使用公开存储桶。
任意一个失效,其余仍起作用。这就是团队所说的“安全是设计本身,而非事后附加的功能”。
不声称的部分
隔离保护你免受页面伤害,也保护我们免受页面伤害,但不把页面作者变成你应信任的人。公开链接依然是公开链接:一个人可以在上面放误导内容,就像在互联网上任何托管平台一样。这是审核问题,而非同源问题。你始终控制最重要的部分:谁可以打开每个页面——从仅自己、到团队、到密码保护、到完全公开。
团队持续加固。目前服务页面上的内容安全策略故意宽松,因为 AI 生成的页面需要合法加载字体和调用 API,而沙箱才是真正承担风险的容器。使上传的 JavaScript 能够安全运行的核心——null-origin 沙箱和独立的无 cookie 源——从第一个页面起就存在,并且不会改变。
关键要点
- 不消毒 HTML,而是隔离运行环境:放弃传统消毒思路,因为消毒会破坏产品功能且无法彻底防御;改为让运行环境本身安全,文件无需信任。
- 沙箱剥夺 same-origin 权限:iframe 沙箱中故意不设置
allow-same-origin,使脚本获得 null origin,无法访问 cookie、localStorage,也无法与父页面交互。 - 独立无 cookie 源:页面内容从另一个不设置 cookie 的源通过短期签名 URL 提供,即使沙箱逃逸也无会话可窃取。
- 严格禁止
allow-scripts与allow-same-origin同时出现:这是保护机制的核心承重线,一旦组合,保护会完全失效。 - 多层防御叠加:沙箱、独立源、签名 URL、应用端 CSP、服务端鉴权、加密存储等多层独立防线,任意一层失效其余仍有效。
- 不解决内容可信问题:隔离防止恶意脚本危害用户和平台,但不阻止页面作者发布虚假或误导内容——这是审核问题,用户可通过权限控制谁可访问页面。
意义与影响
这篇文章为托管用户生成/上传的任意 HTML/JavaScript 提供了一个全新的安全范式。传统上,行业普遍采用“先消毒后托管”的思路,但实践证明这难以应对不断演变的攻击向量,尤其当 AI 生成的内容大量出现时,逐行审查变得不现实。ShareMyPage 的“隔离而非过滤”设计,将安全焦点从内容本身转移到运行环境,通过 null-origin 沙箱和独立无 cookie 源两个关键措施
