资讯文章 资讯 · NEWS
给第三方 JS 加 SRI:目录站那条常被忽略的供应链防线
2026-10-24 约 2,204 字 预计阅读 6 分钟 yiyunkj
给
目录站页面上通常挂着不少别人的东西:统计、广告、地图、播放器、客服插件、字体 CSS。这些资源有一个容易被忽略的事实——它们在你的域名下运行,权限等同于你自己的代码。能读 DOM,能拿 cookie,能改页面内容。
它们的来源多半是公共 CDN。CDN 被拿下、某个包被恶意接管、或者运维误操作换了一份文件,风险就直接落到你头上。这类事在业界有名字,叫供应链攻击,也有够多的先例。

图片来源:网络,版权归原作者所有,侵权请联系删除
SRI 是什么,一句话
Subresource Integrity(子资源完整性)是 W3C 的标准:你在标签里写上资源的加密哈希,浏览器下载完算一遍,对不上就拒绝加载,并返回网络错误。
<script src="https://cdn.example.com/widget@1.2.3/widget.js"
integrity="sha384-oqVuAfXRKap7fdgc..."
crossorigin="anonymous"></script>
几条规则要记住:
- 支持三种算法:
sha256、sha384、sha512。业界普遍推荐 sha384。 - 可以同时写多个哈希,用空格分隔。浏览器会取其中最强的那个算法来比对;同一算法下多个哈希,任意一个匹配即通过——这个特性正好用来做版本滚动。
- 比对失败就是失败,没有「降级加载第二份」这种机制。所以写错哈希的后果是页面上缺一块。
那个必须跟上的属性:crossorigin="anonymous"
这是实践里最高频的一处事故。
按 MDN 的说明:跨域请求使用 SRI 时必须走 CORS 协议,资源提供方要返回合适的 Access-Control-Allow-Origin。原因是如果允许在 no-cors 模式下使用 SRI,攻击者就能通过「给一个特定哈希、然后观察加载成功还是失败」来推测资源的字节内容。所以浏览器对这种情况直接让请求失败。
而 HTML 里从文档加载的资源默认就是 no-cors 模式——也就是说,光写 integrity 不写 crossorigin,脚本会被拦掉。
三个硬性前提
一、URL 必须指向不可变版本
widget@1.2.3/widget.js 可以,widget/latest/widget.js 绝对不行。后者内容随时可能变,哈希第二天就失效。
二、文件内容一变,哈希必须跟着变
这件事必须自动化,放在构建或发布流程里。手改的结果是:某次更新之后某个互动组件在全站失效,而当时值班的人想不起来半年前改过什么。
三、加之前先有清单
在动手之前,得知道站上到底引了什么。按页面类型各扫一遍:
- 首页(通常最多)
- 分类页
- 条目页(容易被忽略,往往多带一两个播放器或地图)
- 后台管理页(这里的资源经常没人管)
把结果落成一份「第三方资源清单」:域名、路径、用途、负责人、是否加了 SRI。这份清单本身就是安全资产。
哪些资源不该加
不是所有第三方脚本都适合 SRI。判断标准很简单:这个资源的内容会不会在我不发版的情况下变化。
| 资源类型 | 适不适合 | 处理方式 |
|---|---|---|
| 固定版本的库文件 | 适合 | 直接加 |
| 字体 CSS、样式文件 | 适合 | link 标签同样支持 |
| 内容会变的统计加载器 | 不适合 | 自托管一份冻结版本,定时更新并同步重算哈希 |
| 由对方动态生成的接口型脚本 | 不适合 | 评估能不能去掉,或改成服务端调用 |
上线流程:先 Report-Only,再强制
第一步:哈希生成自动化
curl -s https://cdn.example.com/widget@1.2.3/widget.js \
| openssl dgst -sha384 -binary | openssl base64 -A
把每个资源的哈希写进构建产物或者模板变量。凡是「人工维护哈希」的方案,三个月后一定会漂移。
第二步:用响应头做兜底
只有写在标签里的 SRI,漏一个就少一层。可以用 Integrity-Policy 系列响应头来约束:
Integrity-Policy-Report-Only: blocked-destinations=(script), endpoints=(integrity-endpoint)
Integrity-Policy: blocked-destinations=(script)
Report-Only 阶段只上报不拦截,通过 Reporting API 端点把「哪些脚本没有带 integrity」收上来;确认清单干净之后再切到强制。有一点要注意:被这个策略管到的 script,连同源的也必须带 crossorigin 和 integrity,否则会被拦掉;另外各浏览器的上报行为不完全一致(有浏览器目前只在控制台打日志、不上报到端点),验证时要分开看。
CSP 是另一半
SRI 只回答「加载哪一份文件」,「从哪些主机加载」要交给 CSP 的 script-src。两条一起用才完整。顺带一句:CSP 里那条老的 require-sri-for 指令,浏览器支持情况一直不理想,别拿它当唯一防线;构建期自动生成加上述响应头是更实际的做法。
季度复检的四件事
- 重跑一遍资源清单,看有没有新加的第三方脚本没进表
- 抽一个脚本本地代理改一个字节验证一次,确认浏览器真的拒绝加载
- 检查所有 external 标签是不是同时有 integrity 与 crossorigin
- 看看有没有资源地址被改成了 latest 这种形式
做完这套,第三方资源就从「我不知道它在干什么」变成了「我只允许这一个哈希版本的它运行」。对一个挂了七八个外部组件的目录站来说,这层收益是很实在的。