隔离运行
翻译器处理的是第三方不可信数据,不能和框架运行在同一个 JavaScript realm。隔离运行把翻译器放进一个不允许同源访问的沙箱 iframe,由它启动专用 Worker;主页面只通过消息通道收发数据。
先说清边界:这是一层用于处理不可信输入的运行边界,不是通用的安全沙箱,也不是进程级或操作系统级隔离。它不能替代签名、准入、能力同意与用户裁决;它只负责把翻译器与外壳的 DOM、存储、网络和原生调用隔开。
相关页面:翻译层、Translate 大插件、装载管线与 guard、信任与签名。
结构
主页面(外壳 realm)
├─ 持有隔离 iframe 的引用与计时器
├─ 代翻译器读取本次输入
├─ 维护输入/输出/墙钟预算
└─ 经 MessageChannel 收发消息
│
▼
沙箱 iframe(不透明源,sandbox="allow-scripts")
├─ 没有同源访问权
├─ 没有访问父页面 DOM 的路径
├─ 没有外壳存储与原生调用
└─ 启动专用 Worker
│
▼
Worker
├─ 装载翻译器 ESM
├─ 实例化 Rust 同源编译的 WASM(需要二进制解析时)
├─ 调用 decode(bytes, options)
└─ 经受限 read(path) 桥请求输入| 组成 | 允许 | 禁止 |
|---|---|---|
| iframe | 执行脚本、启动 Worker、经消息通道通信 | 同源访问、读取父页面、使用父页面存储;网络由启动页的独立安全策略禁止 |
| Worker | 执行翻译器代码、文本与 JSON 映射、调用 WASM | 取得能力对象、持有读取闭包、直接访问宿主文件系统 |
| 主页面 | 代读、计时、预算判定、核验产物 | 在主 realm 执行翻译器入口字节、把迟到结果交给调用方 |
iframe 只设置 allow-scripts,不设置 allow-same-origin。这使它的源成为不透明源:它不能读写外壳的 DOM、Cookie、LocalStorage 或 IndexedDB,父页面也不能直接调用它的内部对象。双方只能经 postMessage 与转移的端口通信;启动页另用独立安全策略禁止网络。
启动与握手
- 主页面创建隐藏 iframe,设置
sandbox="allow-scripts"、referrerpolicy="no-referrer",并建立MessageChannel。 - iframe 内的启动页加载完成后,向父页面发出就绪消息;父页面核对消息来源窗口与不透明源,只接受约定的就绪类型。
- 父页面把端口与本次任务数据转移给 iframe,并发出启动消息。
- iframe 启动专用 Worker,把任务交给 Worker;Worker 装载翻译器并准备读取桥。
- 翻译器开始执行,按需经消息通道请求读取;主页面核对路径、长度与预算后回传字节。
- 结束时 Worker 返回产物或错误;主页面核验后销毁隔离环境。
启动有期限:iframe 未在期限内应答,或常驻会话中的 Worker 未在期限内报告就绪,都按「宿主不可用」结束并撤掉隔离环境。期限用于避免半启动的会话长期占用资源,不表示翻译任务本身的时限。
网页发布构建会让隔离页按部署基址定位 codec-sandbox,而不是假定站点部署在根路径;构建同时输出外壳和信任页的 CSP 文件,由部署方作为响应头下发。没有显式网页源配置的 Tauri release 构建不能直接充当网页部署产物。
主线程代读,不交能力
翻译器拿不到能力对象。它不能调用宿主 API,不能自行打开文件,不能发网络请求,也不能访问外壳存储。需要输入时,它只能发读取请求;由主页面按本次作业获准的目录快照代读。
| 能力 | 用途 | 当前状态 |
|---|---|---|
caps:fs-read | 只允许框架代隔离 codec 读取用户本次选择的输入 | 可用 |
caps:raw-bytes | 隔离专用;把本次输入字节交给代理条目 | 可用 |
caps:net-fetch | 在线翻译所需的网络获取 | 当前不可用 |
规则:
- 必须由主页面代读;禁止向 Worker 传递读取闭包、能力对象或宿主句柄。
- 必须按调用时的获准目录快照检查路径;禁止翻译器扩大读取范围。
- 必须按实际读取长度计入输入预算。
- 翻译器同时至多发出 64 个读请求;每次读取至少按 4 KiB 计入输入预算,零字节文件也不能无限次读取。
- 普通插件不能取得
caps:raw-bytes与隔离专用的caps:fs-read。
预算
隔离会话同时受三项预算约束:
| 项目 | 默认 | 可调上限 | 超限行为 |
|---|---|---|---|
| 墙钟 | 30 分钟 | 240 分钟 | 中止并销毁隔离环境 |
| 输入 | 1 GiB | 8 GiB | 中止并销毁隔离环境 |
| 输出 | 1 GiB | 8 GiB | 中止并销毁隔离环境 |
补充约束:
- 预算值必须是正安全整数;非法值直接拒绝,不按默认值猜测。
- 每次收到数据消息、每次交付读取结果前都重新核对期限;不是只在开始时检查一次。
- 输入与输出预算按实际字节累计;翻译器自报的大小不作为依据。
- 归档解压另有上限:中央目录最多 64 MiB,单条目的压缩数据与解压结果各最多 256 MiB,解压比最多 100:1;输入预算按读入量与解出量取大者计费,读取逐个进行,同一时刻只有一个条目的字节在途,等待队列最多 64 项。
预算不是内存硬限额。 浏览器不提供单个 Worker 的内存上限;框架只能限制自己经手的输入、输出与时间,不能限制 Worker 内已经分配的内存。
codec 如何注册
翻译器以 translate:codec@1 实现位登记,且只能由装载器以代理条目登记:
| 步骤 | 要求 |
|---|---|
| 单元声明 | 清单的登记落点必须指向 translate:codec@1,并声明支持的 formats |
| 判定 | 装载器把该单元判为隔离运行;入口字节只作 Worker 源码 |
| 代理登记 | 以该单元身份登记代理条目;已获准能力只随条目记录,不交给代码 |
| Worker 装载 | Worker 内装载 ESM,核对导出的 formats |
| 形状核对 | 导出 formats 与清单声明不一致,以 invalid 失败 |
| 调用 | 创建本地 read(path) 桥,调用 decode(bytes, options) |
主 realm 不执行翻译器入口。代码单元只能有清单指向的单个打包后 ESM 入口;包内模块之间的相对 import 不提供,避免把未登记文件带进执行路径。
产物核验
Worker 交出的不是「可信产物」,只是待核验的文件表。主页面按标准目录结构整理并逐项核对:
| 核验项 | 要求 |
|---|---|
| 复制来源 | copyFrom 必须经同一获准读取通道取得原始字节,并计入输入预算 |
| 文件指纹 | 按实际字节生成 SHA-256,覆盖翻译器自报的文件表 |
| 清单 | 由框架生成标准 pack.json,不采用翻译器自报的清单 |
| 分类 | 拒绝混入代码入口、能力声明或写边的数据包与资源包 |
| 输出预算 | 最终清单字节也计入累计输出预算 |
| 单包预算 | 单文件不超过 256 MiB、文件数不超过 100,000、总量不超过 1 GiB |
| 预核对 | 单文件、文件数、总量三项与累计输出预算在结果交给主页面之前先核对一次 |
超限时结果不转交主页面。Worker 发来的失败消息只认固定原因,不展示翻译器自拟的说明文字,避免用错误文案误导用户或伪造状态。
产物核验通过也只代表「结构合法、预算合规」。它仍然是未签名数据,装载时须重新走普通准入;需要批准的仍要用户批准,不得伪造签名或绕过 guard。
取消与迟到结果
- 取消时向隔离环境发送关闭消息,等待其自行关闭;超时后直接移除 iframe,端口一并关闭。
- 会话一旦结束,后续到达的消息不再处理,也不会被当成新结果。
- 取消后不继续读取下一份复制来源,不返回半成品。
- 不回传迟到结果:即使 Worker 在取消后算出了内容,主页面也不会交付给调用方。
- 取消不会强制中止来源适配器已经发出的外部读取;这部分读取可能继续完成,但结果被丢弃。
已知限制
| 限制 | 说明 |
|---|---|
| 不是进程级隔离 | 没有操作系统级内存、CPU 或文件描述符隔离;只是浏览器内的执行边界 |
| Worker 内存不受限 | 框架无法限制 Worker 已分配的内存;只能限制输入、输出与时间 |
| 预算不是内存硬限额 | 三项预算约束的是框架经手的字节与时间,不是浏览器进程的总内存 |
| 取消不撤销已发出的外部读取 | 取消只停止后续处理与交付,不能收回来源适配器已发出的请求 |
| 网络能力当前不可用 | caps:net-fetch 尚未开放;在线翻译的获取路径受限 |
| 依赖宿主能力 | 缺少所需 DOM 或消息通道能力时,明确报「宿主不支持」,不用近似方案冒充 |
| 不替代信任机制 | 隔离只缩小翻译器能影响外壳的范围,不判断发布者身份,也不替用户批准产物 |
这些限制必须如实展示。把隔离说成「安全沙箱」会误导用户以为它提供进程级保护;把预算说成「内存上限」会掩盖 Worker 内分配不受控的事实。
隔离不是什么
- 不是
ctx.isolate的服务命名空间隔离。 后者只隔离服务解析命名空间,不是安全边界。 - 不是签名机制。 签名回答发布者与内容是否被改动;隔离回答翻译器运行时能碰到什么。两者互不替代。
- 不是产物的信任背书。 隔离中产生的包仍是普通未签名包,装载时按普通流程判定。
- 不是对所有扩展的沙箱。 插件与 Mod 仍与框架同 realm 运行,属于受审代码。隔离只用于翻译 codec 与确实需要隔离的输入适配路径。
参见
- 翻译层:翻译器的职责、三件分离与产物写出。
- Translate 大插件:服务、codec 与写出器的接口。
- 装载管线与 guard:产物装载时的准入与能力判定。
- 信任与签名:为什么写出成功不等于可以装载。
- 装载原因码:隔离失败与预算超限的对外表现。