第三方脚本是一种供应链风险
第三方脚本是你没写过的代码,由你无法控制的公司提供,在你的页面内以你用户的会话运行——用供应链的话说,它是一个可以随时改变自身代码的依赖,无需发布、无需审查、无需通知。2026 年 10 月,据报道,这一抽象风险变成了具体的损失:一个主要交易站点加载的性能监控脚本被替换成了旨在掏空其用户的脚本。本文梳理被报道的内容、SRI 实际能阻止什么、为什么先锁定版本再做哈希、自托管何时是更好的答案,以及让清单保持诚实的简短审计流程。
据报道,Polymarket 发生了什么
据报道,2026 年 10 月 29 日,Polymarket 的国际站在用户报告账户资金流失后将交易置于仅取消模式,约一小时后恢复交易。据报道,该公司自己的声明将事件归因于一家被攻破的第三方服务商,称已将此事移交执法部门,并承诺让受影响用户得到全额补偿。基于该公司 10 月 30 日复盘的报道称,攻击中被取走的金额约为 270 万美元,计入因交易暂停造成的损失的善意赔付后,退款总额为 390 万美元;报道中引用的独立链上分析追踪到了相近的数字,约为 270 万美元,涉及数百个钱包。这些数字无一在此独立核实——它们是报道的数字,本文也如此对待它们。
据报道,其机制才是可以泛化的部分。报道中引用的研究人员将恶意代码与一家网站性能厂商的真实用户监控(RUM)脚本联系起来——一个小文件,其全部工作就是在访客浏览器中测量页面加载速度,按客户从该厂商的 CDN 嵌入。据报道,该文件被替换成了一个专为目标站点编写的负载:它在已登录会话内工作,使用页面中已存在的该站点自身的交易机制,将头寸和资金转移到攻击者控制的目的地。据报道,其中不涉及智能合约漏洞,也不涉及被盗的密码或私钥——被替换的脚本在浏览器看来就是页面本身。这一叙述的每个细节是否经得起后续审视,对教训而言并不重要:攻击形态——一个厂商文件、被替换一次、被每个访客执行——正是这一类事件的标准形态。
第三方脚本是你的代码,只是发布流程是别人的
经典 script 标签里没有沙箱。从厂商 CDN 加载的代码在你的源内执行,拥有与你自己的包相同的访问权:它能读取和修改 DOM、在输入时读取表单值、用用户的会话调用你的 API,并把找到的东西发到任何地方。OWASP 的第三方 JavaScript 指南直白地列出了三项风险——失去对客户端应用变更的控制、在客户端系统上执行任意代码、向第三方泄露敏感信息——三者都源于这一个事实。你的审查流程、代码冻结和变更委员会止于你自己的构建;厂商的文件可以被厂商编辑、被攻破厂商的攻击者编辑,或被在域名过期后重新注册它的人编辑。
这种不对称正是“我们信任这个厂商”不是一种控制的原因。信任决定你今天接受谁的脚本;它对那个 URL 明天提供什么什么都不说,也在内容变化时给你任何信号。有效的控制是那些要么消除信任(自己提供文件)、要么约束信任(用 Content-Security-Policy 限制任何脚本能做什么、能把数据发到哪里)、要么在每次加载时机械地验证信任的东西——这正是 SRI 的用途。从清单开始:你无法对不知道自己拥有的脚本应用这三者中的任何一个,而一旦算上营销标签、支持组件和监控嵌入,大多数站点拥有的脚本比其所有者预期的要多。
SRI 能阻止什么——以及不能阻止什么
子资源完整性是浏览器机制,由 W3C 规范、由 MDN 记录:页面在标签的 integrity 属性中声明它期望的文件的加密哈希,浏览器对实际下载的内容做哈希,任何不匹配都会拒绝执行该文件。对于跨源文件,标签还需要 crossorigin="anonymous",且服务器必须发送 CORS 标头,浏览器才被允许做此检查。应用于固定库时,SRI 把静默替换变成响亮的失败:到达的文件,要么逐字节就是你审查过的那个,要么根本不运行。如果被报道事件中的监控脚本是以这种方式加载的固定文件,被替换的负载就会检查失败,而不是在用户会话中运行——这一条件句是诚实的表述形式,因为那个特定的嵌入是否可哈希,关乎一个动态的按客户文件,而那正是下面的困难情形。
SRI 不能阻止什么同样重要。它对内容本就应当变化的脚本没有帮助——标签管理器容器、个性化和同意加载器、按客户生成的文件——因为没有稳定的哈希可以锁定。它不评判你哈希过的那个文件:一个忠实交付的脚本在设计上仍可以爱窥探、粗糙或恶意,而 SRI 会永远愉快地验证它。它不覆盖由其他脚本在运行时注入的脚本,不保护从你自己基础设施提供的第一方文件,也对合法脚本运行后收集的数据无能为力。SRI 是一项机械保证——就是这些确切的字节,否则什么都不行——它与 CSP、厂商审查和从一开始就最小化有多少第三方获得会话级访问并列,占有其位置,而不是取而代之。本站的安全头检查为这一配对中的 CSP 一侧评分。
锁定版本,否则哈希无从立足
SRI 有一个先决条件,它让审计失败的次数比恶意还多:URL 必须永远返回相同的字节。在 latest 地址、浮动主版本,或厂商重新生成的按客户文件之上锁定的哈希,会在厂商第一次发布更新时失效——而被自伤的 SRI 失败半夜叫醒过一次的团队,往往会移除该属性而不是修复地址,让页面比未测量时更糟。持久的顺序正相反:先把 URL 改成指名确切版本([email protected]、/v2.4.0/),审查那个版本一次,然后对那个确切文件做哈希。版本升级于是成为刻意的事件——新 URL、新哈希、一次提交——而不是你从用户那里发现的环境变化。
锁定有成本,诚实要求点名它:被锁定的库不再从厂商那里接收静默的安全补丁,因此锁定创造了跟踪发布并刻意升级的义务,与任何 vendored 依赖所带的是同一种义务。这是一种可管理的义务——发布说明和日历提醒就能覆盖它——而且它严格优于它所取代的替代方案:在替代方案中,更新以厂商的节奏未经审查地到达,故障以你的节奏未经宣布地到达。把这一对当作一项实践:锁定让内容可寻址,哈希让它可验证,审计确保两者都没有悄悄腐烂——被 CMS 迁移编辑掉的版本字符串与缺失的哈希一样常见,而且只略难用肉眼察觉。
自托管,以及审计流程
对于最重要的脚本,有一个更直白的选择:自己提供文件。自托管把厂商的服务器、账户和域名从你的运行时供应链中整个移除,让你的正常部署流水线审查并哈希该文件,并在厂商的 CDN 故障时继续工作。取舍是真实的——你放弃厂商的边缘缓存和自动更新,承担自己交付其安全修复的义务,而一些厂商的许可或按客户生成的文件根本不允许这样做——因此明智的划分按关键程度:小型固定库在敏感页面上自托管;留在 CDN 上的锁定并哈希;动态标签既不能锁定也不能托管的,用发布者限制、变更审查和 CSP 来治理;并当场删除任何无人能说明理由的脚本。每删除一个脚本,就是一条你不再拥有的供应链。
让这一切保持为真的流程短到可以真正运行。每季度一次——以及在任何重新设计、CMS 迁移或新厂商之后——对你的关键页面模板运行第三方脚本与 SRI 审计:它列出静态 HTML 引用的每个脚本、哪些是第三方、哪些缺少完整性、哪些未锁定,并为它能抓取到的前若干缺失哈希计算可直接粘贴的 sha384。按影响顺序修复——会话、支付和个人数据页面优先——然后用 SSL 评分器和安全头检查查看传输层和标头层,最后在标签管理器中收尾,那里由运行时注入的脚本是静态审计看不到的:以看待部署日志的同样怀疑审查其发布者列表和变更历史。每季度十五分钟,对抗一个据报道在上述案例中约一小时就造成数百万损失的事件类别。