在 Chrome 里按 Shift+Esc 打开自带的任务管理器,然后只开一个标签页去看一个普通的新闻网站。你大概率会看到不止一行:主文档一行,广告位的 iframe 一行,社交分享按钮的 iframe 又一行,统计脚本所在的 iframe 再一行。一个标签页,四五个操作系统进程。
这不是浪费。这是 2018 年之后浏览器唯一还站得住的安全模型:页面里那条"这段代码不许读那段数据"的规则,不再由软件检查来执行,而是由操作系统的地址空间来执行。
这篇文章要拆的是这条边界怎么划、由谁把守、代价是多少,以及它在哪些情况下根本不起作用。
破除误解
误解一:站点隔离就是"一个标签页一个进程"。
进程分配的单位从来不是标签页,而是一个叫 SiteInstance 的内部对象——同一个浏览上下文组内、来自同一站点的一组文档。所以两个方向的偏离同时存在:
一个标签页可以横跨很多进程。你在 news.example 的页面里嵌了 ads.example.net 的广告框,这个广告框会被搬到另一个渲染器进程里去跑(这叫 OOPIF,out-of-process iframe,跨进程内嵌框架)。一个页面、一棵帧树,物理上散在五个进程里。
反过来,很多标签页可以共用一个进程。你开十个标签页都在 example.com 上,它们大概率共享同一个渲染器进程——因为同站点文档之间本来就允许同步互相脚本操作,拆开反而做不到。
误解二:同源策略已经拦住了跨站读取,站点隔离只是在它上面"再加固一层"。
同源策略(Same-Origin Policy,浏览器里"不同源的文档不能互相读数据"的基本规则)在 2018 年之前是渲染器进程内部的一串 if 判断。它管的是"这段 JavaScript 能不能调用那个 API"。它从来管不了"这些字节在不在同一块地址空间里"。
一旦攻击者能在渲染器进程里执行任意代码(渲染器要解析全世界的 HTML、JavaScript、图片、字体,是浏览器里漏洞最多的地方),那串 if 判断就是可以直接跳过的几条指令。更糟的是 Spectre——它连指令都不用执行,直接从微架构状态里把内存读出来,全程不经过任何检查。
结论很干脆:数据只要进了同一个进程的地址空间,就应当视为已经泄漏。 站点隔离不是"加固同源策略",它是把同源策略的执行者从软件换成硬件。
误解三:站点隔离是按"源(origin)"隔离的。
不是。它按站点(site)隔离,定义是 scheme + eTLD+1——协议加上"可注册域名",忽略子域、端口和路径。https://foo.example.com:8080 属于站点 https://example.com。所以 a.example.com 和 b.example.com 在同一个进程里。
这个粗粒度是对历史的妥协:document.domain 这个远古 API 允许两个子域互相"降级"到同一个域名从而互相脚本操作,如果按源分进程,它们物理上就在两个地址空间里,同步脚本调用根本无法实现。直到 Chrome 115(2023 年) 把 document.domain 的写入默认变成空操作,往源粒度收紧才重新有了可能——那也是 Origin-Agent-Cluster 这个响应头存在的原因。
顺带一个容易忽略的细节:eTLD+1 是查 公共后缀列表(Public Suffix List) 算出来的,而 github.io 在这份列表里。所以 alice.github.io 与 bob.github.io 是两个站点、两个进程——这正是这份列表存在的意义。
先有稳定性,后有安全
2008 年 Chrome 发布时就是多进程的,但最初被摆在台面上的理由不是安全,是健壮性:当时的浏览器一个页面里的脚本死循环、一个插件崩溃,整个窗口连同你写了一半的邮件一起消失。Reis 与 Gribble 在 EuroSys 2009 的论文里把这件事讲清楚了:浏览器已经变成了运行一堆互不相干程序的运行时,那它就该像操作系统一样,给每个程序一个独立的失败域。
于是有了今天这套分工:
| 进程 | 职责 | 权限 |
|---|---|---|
| 浏览器进程(browser process) | 界面、标签管理、导航决策、权限、存储调度 | 完整用户权限,唯一可信 |
| 渲染器进程(renderer) | HTML 解析、CSS 布局、V8 执行 JS、绘制 | 重度沙箱,几乎什么都不能直接做 |
| GPU 进程 | 光栅化、Viz 显示合成器 | 中度沙箱 |
| 网络服务(network service) | DNS、TCP/TLS、HTTP 缓存、Cookie 存取 | 独立进程 |
| 工具进程(utility) | 音视频解码、数据解码等一次性活儿 | 按任务收紧 |
标签页崩了只弹一个 "Aw, Snap!",正是因为死的只是一个渲染器进程,浏览器进程还在。
但这个结构真正的红利在第二层:渲染器既然只负责计算,就可以被剥夺几乎所有系统能力。
沙箱:渲染器被拿走了什么
Barth、Jackson 与 Reis 2008 年那份 Chromium 安全架构报告把浏览器切成两块:一块是"浏览器内核",跟操作系统打交道;另一块是渲染引擎,关在沙箱里、以受限权限运行。
渲染器进程不能打开文件、不能发起网络连接、不能创建子进程、不能读注册表、不能直接往屏幕上画东西。它想要这些能力,只能通过 Mojo(Chromium 的 IPC 系统)向浏览器进程或网络服务申请,由对方代办。
各平台的实现手段完全不同,但目标一致:
- Windows:进程令牌里的 SID 全部改成"仅拒绝"、加入受限的 Job 对象、完整性级别降到 Untrusted、
win32k系统调用锁定,再叠加 ACG/CIG 之类的代码完整性约束。 - macOS:Seatbelt 沙箱策略文件,白名单式地写明允许哪些操作。
- Linux:命名空间沙箱(user / PID / net namespace)把它跟系统其余部分切开,再套一层
seccomp-bpf系统调用过滤器,把可用的系统调用砍到很小一撮。
这里推导出 Chromium 安全模型里最重要的一条工程纪律:渲染器说的话不算数。 渲染器发来的每一条 IPC 都是不可信输入——包括它自称"我是 https://bank.example"这种话。浏览器进程必须自己知道每个渲染器是谁,不能问它。
这条纪律的落地形式就是下一节的主角。
内部结构:BrowsingInstance、SiteInstance、ProcessLock
Chromium 用三个概念完成"谁跟谁共处一个进程"的决策,理解了它们就理解了整个机制。
BrowsingInstance(浏览上下文组):一组能互相持有 window 引用的页面。window.open 打开的新窗口和打开它的页面在同一个 BrowsingInstance 里,iframe 和父页面也是。这个集合是引用可达性定义的——只要还能拿到对方的 window 对象,就还在一个组里。
SiteInstance:一个 BrowsingInstance 内、属于同一站点的文档集合。规则是每个 BrowsingInstance 里每个站点至多一个 SiteInstance——因为同站点文档必须能同步互相访问,必须共处一个进程。
进程分配:一个 SiteInstance 映射到一个渲染器进程;进程被打上 ProcessLock(进程锁)。锁在内容加载之前就打上,之后不可更改,即使渲染器崩溃重启也保留。锁的粒度可以是单个站点、单个源、某个协议,也可以是"允许任意站点"(用于尚未开启完整隔离的场景)。
浏览器进程的守门检查就落在这把锁上:
// 渲染器发来 IPC:把 https://bank.example 的 cookie / localStorage 给我
if (!ChildProcessSecurityPolicy::GetInstance()
->CanAccessDataForOrigin(renderer_process_id, requested_origin)) {
// 一个正常的渲染器不可能发出这种请求——发出即证明它已被攻陷
bad_message::ReceivedBadMessage(host, bad_message::SOME_REASON);
return; // 并且直接终止这个渲染器进程
}
```注意最后那行注释。这不是"拒绝请求然后返回错误",而是杀掉进程。因为在正确实现里,越界请求根本不可能由未被攻陷的渲染器发出,所以它被当成入侵证据处理。Cookie、localStorage、IndexedDB、HTTP 缓存条目、密码管理器的自动填充——所有带站点归属的数据,出口都在这道检查后面。
进程数量不是无限的:Chromium 有一个按可用内存计算的软上限。超过之后,新的同站点 SiteInstance 会被随机复用已有进程,而不是继续开新的。这是一次直白的取舍——在内存压力下,牺牲的是并行度和隔离粒度,不是正确性(同站点复用不破坏安全边界)。
把几种进程模型放在一起看,取舍就清楚了:
| 模型 | 一个进程里可能装下 | 崩溃影响面 | 跨站数据是否共处一个地址空间 | 内存 |
|---|---|---|---|---|
| 单进程 | 全部页面 | 全部 | 是 | 最省 |
| 每标签一进程 | 一个标签的全部帧 | 一个标签 | 是(跨站 iframe 同进程) | 中 |
| 每 SiteInstance 一进程(Chrome 2008 默认) | 同站点文档 + 所有内嵌跨站帧 | 一组同站页面 | 是(内嵌帧同进程) | 中 |
站点隔离 --site-per-process(2018 起桌面默认) | 只有同站点文档 | 一个站点 | 否 | +10–13% |
源隔离(--isolate-origins、Origin-Agent-Cluster) | 只有同源文档 | 一个源 | 否 | 更高 |
第二、三行是重点:在站点隔离之前,跨站 iframe 和它的父页面一直是同进程的。你的银行页面里如果嵌了第三方组件,那段第三方代码和你的银行会话数据共享一块地址空间。这在 2018 年之前被认为可以接受,因为同源策略挡着。
2018 年:同进程内的内存隔离不再可信
2018 年 1 月 3 日,Spectre 与 Meltdown 公开(Spectre 变体一 CVE-2017-5753、变体二 CVE-2017-5715,Meltdown CVE-2017-5754)。对浏览器打击最直接的是变体一:边界检查绕过,因为它可以用纯 JavaScript 触发。
机制拆开只有四步,经典的 gadget 长这样:
if (index < simpleByteArray.length) { // ① 先用合法 index 反复训练分支预测器
index = simpleByteArray[index | 0]; // ② 越界那次,CPU 推测条件成立,先读了再说
index = (index & 1) * 4096; // ③ 把读到的某一位放大成 4 KB 的地址偏移
localJunk ^= probeTable[index | 0]; // ④ 拿它访问探测数组,在缓存里留下痕迹
}
```关键在于:架构层面上 ②③④ 从未发生。分支预测错了,CPU 会把寄存器和内存的改动全部回滚,程序看起来什么都没做。但 ④ 已经把 probeTable 的某一个 4 KB 页拉进了 L1 缓存,而缓存不在回滚范围内。接下来逐页读 probeTable 并计时,访问最快的那一页,就编码了越界读到的那一位。重复足够多次,逐位重建整块进程内存。
这件事对浏览器安全模型的杀伤力在于:这条读取路径不经过任何软件检查。 V8 的类型检查、堆分区、边界断言、同源策略的那串 if——全部在架构层,全部被绕过。进程地址空间里的任何字节,对同进程的 JavaScript 都是可读的。
Chrome 最初的反应是抬高信噪比门槛:把 performance.now() 的精度降到 100 微秒量级并加入抖动,2018 年 1 月在桌面关掉 SharedArrayBuffer(用两个共享内存的 worker 可以自己造出纳秒级计时器)。
这些都不是修复,只是加噪。 2021 年 3 月 Google 自己发布了 leaky.page 演示:在 Chrome 88、Intel i7-6500U(Skylake)上,只用标准 JavaScript 与被降精度的计时器,泄漏速率约 1 kB/s;改动很小就能在 Apple M1 上跑起来。计时器精度只是常数因子,不是边界。
V8 层面的软件缓解(推测屏障、指针掩码之类)被评估过,代价是全局性能损失且覆盖不完整。剩下的答案只有一个,而且是唯一的一个:
别把跨站数据放进同一个地址空间。
Chrome 63(2017 年 12 月)已经把站点隔离作为企业策略提供出来;Spectre 之后,Chrome 67(2018 年 7 月)在 Windows、macOS、Linux、Chrome OS 上为 99% 的用户默认开启。Reis、Moshchuk 与 Oskov 在 USENIX Security 2019 上记录了这次迁移。
跨站通信被重新组织
把一棵帧树拆到多个进程里,代价不在"多开几个进程",而在页面本来靠同进程内的指针完成的一切,现在都要跨进程重做。
在站点隔离下,一个渲染器进程里只有属于它自己站点的真实帧(有完整 DOM、样式、布局树);帧树里其他进程的帧被替换成占位帧(浏览器进程侧是 RenderFrameProxyHost,渲染器侧是远端帧对象)。占位帧只保留同源策略本来就允许跨源访问的那一小撮接口:postMessage、写 location、读 frames.length、读 closed。
| 操作 | 同站点 iframe(同进程) | 跨站点 iframe(OOPIF) |
|---|---|---|
读 child.document | 同进程指针解引用 | 同源策略本来就禁止,无需跨进程 |
postMessage | 进程内投递 | Mojo 消息,经浏览器进程路由 |
读 frames.length / closed | 直接读 DOM | 读占位帧上由浏览器进程同步过来的镜像状态 |
| 布局与绘制 | 一棵布局树,一份合成帧 | 各进程各出一份合成帧,由 GPU 进程里的 Viz 按 surface ID 聚合成最终画面 |
| 命中测试与输入投递 | 进程内遍历树 | 浏览器进程读 GPU 侧的命中测试数据,决定把事件发给哪个渲染器 |
| 页内查找 / 打印 / 无障碍树 | 遍历一棵树 | 向所有相关进程分发请求,收集结果再拼装 |
最后三行是真正吃工程量的地方。"这次点击落在哪个帧上"原本是一次树遍历,现在变成了跨进程的几何信息汇总;"页内查找"原本是一次深度优先搜索,现在是一次分布式收集与结果排序;无障碍树要从 N 个进程各取一段再缝成一棵。这些改造从 2010 年代中期开始做,跨越了很多个版本才收敛。
还有一类隐性代价:原本同步的语义必须重新设计。同进程时,写一个跨源 iframe 的 location 是一次函数调用,返回即生效;跨进程时它是一条消息,调用方拿不到确认。凡是依赖"写完立刻生效"的老代码,都会在这里露出时序假设。
光锁进程还不够:数据不该进来
把渲染器锁死在一个站点上,只解决了"它主动问浏览器要别人的数据要不到"。但渲染器有另一条路:自己去拉。
<img src="https://bank.example/statement">、<script src="...">、fetch(url, {mode: 'no-cors'})——这些请求同源策略都允许发出,只是不允许 JavaScript 读响应内容。可在旧模型里,响应字节确实进了渲染器的地址空间(图片得解码、脚本得解析)。同源策略挡住的只是"读"这个 API 动作。Spectre 面前,这层区分毫无意义。
于是站点隔离必须配一套入口过滤:CORB(Cross-Origin Read Blocking,跨源读取阻断),后来演进为 ORB(Opaque Response Blocking,也叫 CORB++)。
它的判定跑在网络服务进程里,逻辑是:这个跨站响应,是不是 HTML、XML 或 JSON?依据是 Content-Type、X-Content-Type-Options: nosniff,以及对响应体开头做内容嗅探(防止把 HTML 标成 application/octet-stream 蒙混过关)。是,就在字节到达渲染器之前掐掉——CORB 的做法是给渲染器一个空响应,ORB 改成直接返回网络错误。
为什么只挡这三类?因为图片、脚本、样式表、字体、音视频是合法的跨站子资源,网页离了它们就散架。这就是这道防线的天花板,也必须诚实说明:
- 它是启发式,不是定理。JSON 如果伪装成 JavaScript(比如老式 JSONP 接口),CORB/ORB 就挡不住。
- 被合法放行的跨站资源(一张图、一个脚本)确实在渲染器的地址空间里,Spectre 照样能读它们。
第二条正是 CORP、COEP 存在的理由——单靠浏览器猜是不够的,得让资源方自己表态。
那些看似零散的响应头,是同一个模型的不同侧面
CORS、SameSite、CORP、COOP、COEP,看起来是五套互不相干的规范。放进上面的模型里,它们其实只在回答两个问题:什么字节允许进入这个进程?哪些进程之间还持有引用?
| 机制 | 回答的问题 | 强制点 | 缺席的后果 |
|---|---|---|---|
SameSite cookie | 跨站请求要不要附带凭证 | 网络服务 | 跨站拉回的响应带着登录态,内容是私密的 |
| CORS | 哪些跨源读取被资源方明确许可 | 网络服务(Chrome 79/80 起移出 Blink) | 检查跑在渲染器里,被攻陷的渲染器直接跳过 |
| CORB / ORB | 不该被读的响应体能否进入渲染器 | 网络服务 | 跨站 HTML/XML/JSON 落进攻击者的地址空间 |
Cross-Origin-Resource-Policy | 我这份资源允不允许被别人的进程装进去 | 网络服务 | 图片、脚本等无法拒绝被任意页面嵌入 |
Cross-Origin-Opener-Policy | 打开我的窗口还能不能持有我的引用 | 浏览器进程(决定 BrowsingInstance 归属) | 引用关系强制两方留在同一组,进程无法分开 |
Cross-Origin-Embedder-Policy | 页面里是否还有未经同意的跨源字节 | 网络服务 + 渲染器 | 无法证明进程"干净" |
其中两处值得单独说。
CORS 从 Blink 搬到网络服务(Chrome 79 开始灰度,2020 年初推开,业界叫它 OOR-CORS)不是重构洁癖。CORS 判定如果跑在渲染器进程里,那么"渲染器不可信"这条纪律就自相矛盾了——被攻陷的渲染器可以直接把判定结果改成通过。安全检查必须跑在被检查者管不着的地方,这条原则在站点隔离里被贯彻了一遍又一遍。
COOP 是唯一直接影响进程分配的头。 因为 BrowsingInstance 是由引用可达性定义的:只要 A 还能拿到 B 的 window,它们就在同一组,浏览器就不能自由地把它们放到不同的隔离域里。Cross-Origin-Opener-Policy: same-origin 的作用就是切断这条引用边——跨源的打开者拿到的 window 变成空对象,双方进入不同的 BrowsingInstance,从而可以被彻底分开。
而 COOP 与 COEP 同时满足,页面就进入 cross-origin isolated 状态(self.crossOriginIsolated === true),换回被 Spectre 收走的能力:SharedArrayBuffer(Chrome 92 起桌面强制要求跨源隔离)、更高精度的 performance.now()、performance.measureUserAgentSpecificMemory()。
这笔交易是理解整套设计的钥匙:浏览器不再无条件提供精确计时和共享内存,而是给它们标了价——你先证明自己的进程里没有别人的数据,才配拿回测量微架构状态的工具。
至于 SameSite:它的时间线本身就说明了这套模型的推进成本。Chrome 80(2020 年 2 月)开始把无 SameSite 属性的 cookie 默认当作 Lax,2020 年 4 月因疫情期间不宜制造线上故障而回滚,7 月重启,到 Chrome 91(2021 年 5 月) 才彻底落定。
代价与失效场景
内存:Chrome 67 默认开启后,官方给出的数字是真实负载下约 10–13% 的总内存开销。Android 上这个代价扛不住,所以策略是有选择的:Chrome 77 起只对用户登录过的站点做隔离,且要求设备内存 ≥ 2 GB,开销约 3–5%;Chrome 92 扩展到使用第三方登录(OAuth)的站点和带 COOP 头的站点,同时把所有扩展彼此隔离。Android WebView 至今没有站点隔离。
这不是纯亏:进程变多的同时每个进程更小、生命更短、内部锁竞争更少。但账面上仍然是花内存买边界。
工程复杂度:前面讲的输入路由、合成、查找、无障碍、同步语义改造,是数年的持续投入。这个代价高到足以解释为什么其他引擎跟进得那么慢——Firefox 的 Fission 从 2021 年 11 月的 Firefox 94 开始在桌面逐步启用,移动端要更晚;WebKit 的站点隔离直到 2024–2025 年才推进到落地阶段。
它管不了的事,必须说清楚:
- 同站点的 XSS:攻击代码和目标数据本来就同站点、同进程。站点隔离在这里一点忙帮不上,防线仍然是 CSP、输出转义、
HttpOnly。 - 子域互信:站点粒度意味着一个被攻陷的
blog.example.com与www.example.com共处一个进程。想收紧只能上Origin-Agent-Cluster或--isolate-origins,而前者只是给浏览器的提示,并不保证单独分进程。 - 浏览器进程、GPU 进程、网络服务自身的漏洞:它们不在渲染器沙箱里。站点隔离把渲染器的爆炸半径收窄了,但没有收窄这几个。
- 合法进入的跨站资源:图片、脚本、字体仍在你的进程里,Spectre 依然读得到。补救手段是资源方自己发 CORP,以及页面用 COEP 拒绝一切未表态的资源。
- 进程上限之后的复用:内存吃紧时同站点复用是安全的,但意味着隔离粒度实际上随系统压力浮动。
一句话概括这个模型的适用范围:站点隔离把"跨站点"这条线画成了物理的;线内的一切,仍然只有软件在守。
跨域连接
- 集合论:站点隔离的第一步不是写代码,而是在全部文档上定义一个等价关系——
scheme + eTLD+1相同者为一类,进程分配就是这个等价关系的商映射。这解释了为什么"站点"而非"源"的选择如此关键:等价类越粗,商集越小、进程越省,但每个类内部就有越多本该互不信任的成员被强制同居。document.domain之所以必须先死,是因为它允许运行时修改等价关系本身,而任何一个建立在划分之上的静态资源分配都无法容忍类的边界在运行中漂移。公共后缀列表则是这个划分的外部公理集——github.io被列入,alice.github.io与bob.github.io才落进不同的类。 - 麦克斯韦妖:Spectre 的哲学教训与麦克斯韦妖的解决殊途同归——只要一个过程在物理上留下了可被测量的痕迹,逻辑上宣布"这次操作已被撤销"就没有意义。妖必须测量分子速度,而测量本身有热力学代价;Spectre 的攻击者必须测量缓存命中时间,而 CPU 的架构状态回滚从不回滚缓存。这解释了为什么降低计时器精度注定只是缓兵之计:它增加的是提取信息所需的观测次数,而不是消除信息本身。安全边界要成立,必须落在痕迹无法跨越的地方,也就是不同的地址空间。
- 网络科学:BrowsingInstance 的本质是一张"窗口引用图"的连通分量——节点是浏览上下文,边是
window.open与 iframe 建立的引用,同一连通分量必须共享隔离域。于是安全工程变成了图上的操作:COOP 的全部作用就是删掉一条边,让连通分量裂开,进程才可能分开。这也解释了一个反直觉的现象:一个页面的隔离程度不由它自己决定,而由谁引用了它、它又引用了谁决定——这正是网络科学的核心论断,节点的性质由拓扑位置而非自身属性决定。 - 市场失灵与公共品:站点隔离的成本由浏览器厂商与用户设备承担(10–13% 内存),收益却弥散到所有站点,典型的正外部性。而 CORP/COOP/COEP 这类必须由站点自己部署的机制则陷入集体行动困境:单个站点部署它们的私人收益微小,代价却是真实的兼容性风险,于是采纳率长期偏低。浏览器的应对手段正是公共品供给的经典解法——先由平台无条件供给能供给的部分(默认开启的进程隔离),再用"能力换承诺"制造私人激励(想要
SharedArrayBuffer就得先做跨源隔离),把外部性内部化。 - 何为真理:Chromium 安全模型里那条"渲染器说的话不算数",是一条关于证言可靠性的认识论判断——一个来源如果有动机且有能力伪造断言,它的自我陈述就不能构成知识的基础。渲染器自称"我是某银行的页面",与它实际被锁在哪个站点上,是两件必须分开的事:真值判定的权力必须掌握在被判定者无法触及的地方。CORS 检查从渲染器搬进网络服务,是同一条原则的第二次应用;ProcessLock 在内容加载前就打上且不可更改,是第三次。
参考文献
- Barth, A., Jackson, C., Reis, C. & The Google Chrome Team. The Security Architecture of the Chromium Browser (2008). (浏览器内核 / 渲染引擎两分与沙箱威胁模型的原始论述)
- Reis, C. & Gribble, S. D. Isolating Web Programs in Modern Browser Architectures. EuroSys (2009). (多进程浏览器架构的动机与程序边界定义)
- Kocher, P. 等. Spectre Attacks: Exploiting Speculative Execution. arXiv (2018);IEEE Symposium on Security and Privacy (2019). (变体一 gadget 与缓存计时提取的原始描述)
- Reis, C., Moshchuk, A. & Oskov, N. Site Isolation: Process Separation for Web Sites within the Browser. USENIX Security Symposium (2019), pp. 1661–1678. (站点隔离全量上线的设计、开销与限制)
- The Chromium Projects. Site Isolation 与 Process Model and Site Isolation(
docs/process_model_and_site_isolation.md)。(SiteInstance、BrowsingInstance、ProcessLock 的权威定义) - W3C TAG. Post-Spectre Web Development (2021). (COOP/COEP/CORP 为何存在,以及站点该怎么配)
延伸阅读
- Google Online Security Blog. Mitigating Spectre with Site Isolation in Chrome (2018-07-11). (Chrome 67 默认开启时的官方说明与开销数字)
- Google Online Security Blog. A Spectre proof-of-concept for a Spectre-proof web (2021-03-12),配套演示
leaky.page。(降精度计时器下仍可行的实测泄漏) - web.dev. Making your website "cross-origin isolated" using COOP and COEP. (从站点侧一步步启用跨源隔离的操作指南)