1. 简介
本节为非规范性内容。
存储通常按源进行分区,以保护用户的安全和隐私。这是正确的默认设置,但对于一小类体积非常大、极为流行且公开分发的资源而言并不合适——AI 模型、WebAssembly 模块、JavaScript 库、游戏引擎和大型 Web 字体——无论由哪个网站请求,它们逐字节都是同一个文件。当两个互不相关的源分别依赖同一个 8 GB 模型时,按源分区的存储会迫使用户下载并保留该模型两次,这会浪费用户的带宽、存储空间和电量,也会浪费整个网络的资源。
跨源存储(COS)是一种内容寻址缓存,它以加密哈希而非 URL 为键,使用户代理能够在选择使用它的各个源之间共享此类资源的单个已存储副本。将资源存储到 COS 中始终是存储源明确选择加入的行为,而且用户代理可以拒绝确认资源是否存在,即使对于本来允许读取它的源也可以如此;参见§ 7 隐私 和安全注意事项。
本规范的入口点是 requestFileHandle()
方法,该方法通过 crossOriginStorage
暴露:
const hash= { algorithm: 'SHA-256' , value: '8f434346648f6b96df89dda901c5176b10a6d83961dd3c1ac88b59b2dc327aa4' , }; try { const handle= await navigator. crossOriginStorage. requestFileHandle( hash); const file= await handle. getFile(); // 对该文件执行某些操作。 } catch ( err) { if ( err. name=== 'NotFoundError' ) { // 文件(可披露地)不在跨源存储中;改为从网络获取。 } }
本规范复用文件系统标准 [FS]中的 FileSystemFileHandle、
FileSystemWritableFileStream
以及相关基础设施,但其作用域是一个专用的、跨源共享的文件系统,而不是单个源的私有文件系统或用户可见文件系统。
本规范仅定义命令式 JavaScript API。相关提案将同一个底层缓存与声明式标记集成——HTML crossoriginstorage 属性作用于
link
和
script,
JavaScript 的 crossOriginStorage 导入属性,以及 CSS
cross-origin-storage() <request-url-modifier>——每一种都在其宿主语言自己的规范中定义;参见§ 8 与其他规范的
集成。
2. 概念
2.1. 哈希
- 算法
-
一个字符串, 用于命名 [WEBCRYPTO] 所识别的哈希算法,例如“
SHA-256”。 - 值
algorithm
字典成员的类型是普通
DOMString,而不是 HashAlgorithmIdentifier,
即使它的值空间恰好就是 [WEBCRYPTO]
的哈希算法所接受的名称集合。HashAlgorithmIdentifier
是
(object or DOMString)——它还允许使用 object,该对象由 [WEBCRYPTO] 规范化为
Algorithm
形状的
字典,使调用方能够向需要额外参数的算法传递参数
(例如 {name: "HMAC", hash: "SHA-256"})。哈希算法不接受这类参数;
algorithm 会作为内容寻址
键的一部分被存储、比较并往返传递,而不是由单个操作一次性使用,并且本规范示例中的每一种用法都是裸字符串;在这里允许任意对象不会增加任何能力,反而会使相等性和序列化
变得不必要地复杂。因此,DOMString 是该字段更窄且更正确的类型。
如果两个 COS 哈希值的算法 值以 ASCII 不区分大小写的方式匹配,并且其值完全相等,则它们相等。
注:与大小写未受规范性约束的算法不同, 值已经被规范性地要求为小写(见上文),因此对它的比较是普通 字符串比较,而不是 ASCII 不区分大小写的比较。
内容寻址 存储是指其条目以 COS 哈希的相等性为键,而不是以 URL 或 名称为键的存储:具有相同字节且使用相同哈希算法的两个文件属于同一个条目,无论有多少个源 存储过它们,也无论它们是从多少个 URL 获取的。
2.2. COS 条目
每个用户代理都有一个 COS 注册表,它是从 COS 哈希值到 COS 条目结构的映射, 并由所有使用跨源存储的源共享。一个 COS 条目具有以下项:
- 哈希
-
一个 COS 哈希。
- 字节
- 状态
-
“
pending”或“written”。条目最初为“pending”, 并在字节首次设置时恰好一次变为“written”。 - 源
-
声明的共享作用域:可以是“
*”(任何源)、一个源字符串列表 (仅这些源),或 null(仅同站源)。由第一个写入方设置,并且 可以升级但绝不能降级。 - 存储源
-
一个由源组成的集合,其中每个源都至少成功完成过一次对该条目字节的 写入。该集合会跨页面加载持久保存,并随时间 增长;除 § 5.2 逐出中所述情况外,绝不会缩小。位于 存储源中的源始终可以通过
requestFileHandle()获得该条目的句柄,而不受源或 该条目的哈希是否位于公共哈希列表 (PHL)中的影响。 参见§ 3.4.1 原始存储方访问。
用户代理具有关联的 跨源存储队列,它是 启动一个新的并行队列的结果。对 COS 注册表的所有操作都必须 入队到该队列中,以便它们按照 入队 顺序执行且不会交错。
2.3. 公共哈希列表
确认某个哈希存在于跨源存储中,本身就可能泄露用户的浏览历史信息(参见§ 7.2 跨站探测)。这种风险特别针对全局作用域的条目——其源为列表或 null
的条目,其披露范围已经由存储源的明确选择加以限制,但“*”可能会将条目暴露给 Web
上的任何源。为了限制这一特定风险,作用域为“*”的资源只有在其哈希通过另一个独立门控时,才可向其存储源之外的源披露:即它必须位于 公共哈希列表
(PHL)中。该列表是一个厂商中立、由实现定义的 COS 哈希
值许可列表。只有当某个哈希达到类似 k-匿名的流行度门槛时,它才会被加入公共哈希列表——例如,以字节完全相同的形式出现在达到某个最小数量的独立源中——这样,确认它存在于共享缓存中就不会透露任何特定用户的信息。
如果一个 COS 哈希 hash 与用户代理当前公共哈希列表快照中的某个条目相等,则该哈希位于公共 哈希列表中。
公共哈希列表的检索协议、 更新频率、数据格式、流行度阈值和治理方式 作为本规范的配套产物进行了详细设计,其理念类似于 [URL] 标准的公共后缀列表是一个配套数据文件,而非 规范性文本 — 请参阅本仓库中的 公共哈希列表说明文档。该设计 提议由 WHATWG 进行治理,参考公共后缀列表跨厂商、 滚动发布的先例,并以独立验证的普遍性作为准入标准 — 这是上述 k-匿名性风格门槛的一个具体实例,该门槛仅在哈希被纳入列表时离线应用一次, 而不是用户代理针对每次查询重复执行的检查。此方案目前尚未在 本提案之外建立。列表本身的一个早期、非规范性代码原型 维护在本仓库的 public-hash-list/implementation/ 中 — 这是一个务实的 临时存放位置;一个专用的跨厂商仓库仍然是 PHL 说明文档中描述的治理目标。 本规范仅依赖于某个哈希是否 在 PHL 中,而不依赖于任何特定的 检索机制或治理模型,因此无论该设计如何 演进,本规范都保持正确。
3.
CrossOriginStorageManager
接口
[Exposed =(Window ,Worker ),SecureContext ]interface {CrossOriginStorageManager Promise <FileSystemFileHandle >requestFileHandle (CrossOriginStorageRequestFileHandleHash ,hash optional CrossOriginStorageRequestFileHandleOptions = {}); };options dictionary {CrossOriginStorageRequestFileHandleHash required DOMString ;value required DOMString ; };algorithm dictionary {CrossOriginStorageRequestFileHandleOptions boolean =create false ; (DOMString or sequence <DOMString >); };origins interface mixin { [NavigatorCrossOriginStorage SameObject ,SecureContext ]readonly attribute CrossOriginStorageManager crossOriginStorage ; };Navigator includes NavigatorCrossOriginStorage ;WorkerNavigator includes NavigatorCrossOriginStorage ;
每个 Navigator
和 WorkerNavigator
对象都具有关联的 CrossOriginStorageManager
对象。crossOriginStorage 获取器
的步骤是返回this 的关联
CrossOriginStorageManager。
每个 CrossOriginStorageManager
对象都具有关联的源,在创建该对象时,将其设置为
this 的相关设置对象的源。
3.1. requestFileHandle()
方法
- handle = await navigator . crossOriginStorage .
requestFileHandle(hash) -
如果由 hash 标识的文件存在于跨源存储中,并且可向调用源披露,则返回该文件的句柄。否则,以 “
NotFoundError”DOMException拒绝。出现“NotFoundError” 并不能证明该文件在物理上不存在于跨源存储中;参见§ 7.3 可用性门控。调用方可以将其视为“改为从网络获取此文件”。 - handle = await navigator . crossOriginStorage .
requestFileHandle(hash, {create: true }) -
返回一个可用于将由 hash 标识的文件写入跨源存储的句柄;如果尚不存在条目,则创建一个新条目,默认仅限同站源。 无论条目是否已经存在,调用方都需要通过该句柄的
createWritable()方法写入完整文件内容;此时用户代理会验证写入的字节是否哈希为 hash, 否则以“DataError”DOMException拒绝。此要求可防止源将创建请求用作判断 hash 是否已经存在的预言机。 - handle = await navigator . crossOriginStorage .
requestFileHandle(hash, {create: true,origins: "*" }) -
与上文相同,但还会在条目写入后,使该条目可向对 hash 的请求通过 § 7.3 可用性门控的任何源披露。
- handle = await navigator . crossOriginStorage .
requestFileHandle(hash, {create: true,origins: ["https://a.example", "https://b.example"] }) -
与上文相同,但将披露严格限制为列出的源(除此之外,还包括调用源以及已经位于存储源中的任何源)。
requestFileHandle(hash, options)
方法的步骤如下:
-
令 result 为一个新的 promise。
-
如果 global 的关联
Document(若有)不获准使用 “cross-origin-storage” 策略控制功能,则:-
在给定 global 的情况下,将一个全局任务排入队列,该任务位于DOM 操作任务源上,用“
NotAllowedError”DOMException拒绝 result。 -
返回 result。
-
-
令 validationFailure 为在给定 hash 和 options 的情况下运行验证 COS 请求所得的结果。
-
如果 validationFailure 不为 null:
-
返回 result。
CrossOriginStorageRequestFileHandleHash
hash
和 CrossOriginStorageRequestFileHandleOptions
options 的情况下验证 COS
请求,返回 null 或 TypeError:
-
如果 hash["
algorithm"] 不是 [WEBCRYPTO] 所识别的哈希算法 名称,则返回一个新的TypeError。 -
当 hash["
algorithm"] 以 ASCII 不区分大小写的方式 匹配“SHA-256”时,如果 hash["value"] 不匹配正则表达式/^[0-9a-f]{64}$/,则返回一个新的TypeError。注:未来的哈希算法可以定义不同的预期摘要长度;本规范仅对“
SHA-256”施加规范性约束,与其在各个示例中的用法一致。 -
返回 null。
3.2. 读取文件
-
如果 entry 不为 null,并且 entry 的状态为“
pending”:-
在给定 global 的情况下,将一个全局任务排入队列,该任务位于DOM 操作任务源上,用“
NotAllowedError”DOMException拒绝 result。 -
返回。
注:正在写入的哈希会被刻意处理为与不存在的哈希不同,以防调用方将进行中的写入误认为真正的缓存未命中,并仅为写入该文件而开始重复、并发下载一个非常大的文件。参见§ 3.3 创建和写入文件。
-
-
令 disclosableEntry 为在给定 entry 和 origin 的情况下运行应用可用性 门控所得的结果。
-
如果 disclosableEntry 为 null:
-
在给定 global 的情况下,将一个全局任务排入队列,该任务位于DOM 操作任务源上,用“
NotFoundError”DOMException拒绝 result。 -
返回。
-
-
令 handle 为创建一个新的
FileSystemFileHandle[FS]所得的结果,其 定位器在 跨源存储文件系统内寻址 disclosableEntry,并位于 realm 中。 -
在给定 global 的情况下,将一个全局任务排入队列,该任务位于DOM 操作任务源上,以 handle 兑现 result。
-
如果 entry 为 null,则返回 null。
-
如果 origin 位于 entry 的存储 源中,则返回 entry。
注:原始存储方以及任何已成功写入过该条目的源始终可以重新读取该条目。参见§ 3.4.1 原始存储方 访问。
-
如果 entry 的源为“
*”:-
如果 entry 的哈希不位于 PHL 中,则返回 null。
注:公共哈希列表门控仅在此处应用于全局作用域的条目——这是披露原本可能触及 Web 上任何源的唯一情况。它不适用于下面的列表作用域和同站作用域情况:对于这些情况,存储源已经作出了明确且有界的披露决定,如果还要求额外的全局普遍性,就会使普通的受限共享(参见§ 3.4 资源可见性升级)依赖于 对通常属于专有资源的内容进行不相关的公共策展。参见 § 7.2 跨站探测。
-
返回 entry。
-
-
返回 null。
3.3. 创建和写入文件
由完成创建请求所创建的 FileSystemFileHandle
具有关联的
请求的源,其值是验证并存储在通过该句柄成功写入后,将尝试把条目的源升级到的值。
CrossOriginStorageRequestFileHandleOptions
options、一个全局对象
global 和一个 Realm realm 的情况下完成
创建请求:
-
令 requestedOrigins 为在给定 options["
origins"] 的情况下运行规范化 请求的源所得的结果。 -
如果 entry 为 null:
-
令 handle 为创建一个新的
FileSystemFileHandle[FS]所得的结果,其 定位器在 跨源存储文件系统内寻址 entry,并位于 realm 中。 -
将 handle 的请求的源设置为 requestedOrigins。
注:无论 entry 是否已经存在,也无论它是否已经为“
written”,都会返回 handle。调用方仍需要通过 handle 提供完整的文件字节,这样写入操作便无法用于检测先前是否存在该文件(参见 § 7.2 跨站探测),并且可以在接受对更宽松 源 值的请求之前进行验证;参见 § 3.4 资源可见性升级。 -
在给定 global 的情况下,将一个全局任务排入队列,该任务位于DOM 操作任务源上,以 handle 兑现 result。
*”、一个由
源组成的列表,或 null:
-
如果 origins 不存在,则返回 null。
-
如果 origins 为“
*”,则返回“*”。 -
令 list 为 « »。
-
对于 origins 中的每个 candidate(单个 字符串被视为仅含一个元素的列表):
-
令 candidateOrigin 为在 candidate 上运行基本 URL 解析器 所得的源。
注:验证 COS 请求已经确认每个 candidate 都会解析为非不透明源。
-
-
返回 list。
注:在此处进行去重,而不是仅在稍后合并 到现有条目时才去重,可使源从条目首次创建的那一刻起就不含重复项,因此以后对它的每次克隆也都不会包含重复项。
当通过对其定位器
寻址某个 COS 条目的句柄调用
createWritable()
所获得的 FileSystemWritableFileStream
被关闭时 [FS],用户代理必须在该关闭操作的 promise 被兑现之前运行下面的验证并存储
步骤。
注:无论关闭是如何触发的,这都适用——可以是显式调用
close(),
也可以是 pipeTo()
调用到达其源的末尾,而后者默认也会关闭其目标。在流变为关闭状态时,[STREAMS]
不区分这两种情况,本算法同样不区分。
-
令 computedValue 为使用 entry 的算法所命名的算法,依照 [WEBCRYPTO] 对 bytes 计算得到的小写十六进制摘要。
-
如果 computedValue 与 entry 的值不完全相等:
-
用“
DataError”DOMException拒绝关闭操作的 promise,并保持 entry 不变。 -
中止这些步骤。
-
文件系统标准
[FS] 尚未定义一个扩展点,使依赖它的规范能够将额外的逐次写入验证挂接到 FileSystemWritableFileStream
的关闭步骤中。在这种挂接机制出现之前,本节直接描述所需行为;预计未来的修订版会与 [FS] 进行正式集成。
3.4. 资源可见性升级
COS 条目的可见性可以升级,但绝不能降级。
*”、一个由
源组成的列表,或 null requestedOrigins 的情况下升级
资源可见性:
-
如果 requestedOrigins 为 null,则返回。
注:省略
origins绝不会缩小现有条目的范围;它只请求同站可用性,而每个条目已经至少具有同等程度的可用性。 -
如果 entry 的源为“
*”,则返回。注:已经全局可用的条目不能被后续写入方限制。当本注释适用且 requestedOrigins 不为“
*”时,用户代理应在控制台中记录警告,告知开发者所请求的限制未被应用。 -
如果 requestedOrigins 为“
*”:-
将 entry 的源设置为“
*”。 -
返回。
-
-
如果 entry 的源为 null:
-
将 entry 的源设置为 requestedOrigins。
-
返回。
-
-
对于 requestedOrigins 中的每个 candidateOrigin:
-
将 entry 的源设置为 merged。
注:新网站——而不仅仅是原始存储方——也可以扩大条目的源范围,只要它也提供能够哈希为该条目哈希的字节。这是有意为之: 任何已经拥有某个哈希的正确字节的网站,从构造上来说,都与原始存储方一样有权说明该哈希表示什么。
3.4.1. 原始存储方访问
成功完成过对某个 COS 条目写入的源——即该条目的存储源中的任何源——始终可以随后通过 requestFileHandle()
获得其句柄,而不受该条目的
源值影响,也不受其哈希是否位于 PHL 中的影响。
这与 Cache API 的模型一致:源始终能够访问自己存储的内容。
4. 跨源存储文件系统
跨源存储文件系统是一个文件系统根,不同于任何 源的存储桶文件系统或本地文件系统访问根;其 条目与 COS 注册表中的COS 条目项一一对应。
跨源存储文件系统不使用 [FS] 的普通逐次调用权限检查
模型:从中获得的每个 FileSystemFileHandle
在返回给脚本之前,都已经由
§ 3.2 读取文件或§ 3.3 创建和
写入文件完全授权,因此对这类句柄调用
getFile()
或 createWritable()
绝不会触发额外的权限提示。
寻址某个 COS 条目
entry 的 FileSystemFileHandle
可以在 entry 的
状态仍为
“pending”时存在——例如,在
创建请求之后、相应写入完成之前。
对这类句柄调用 getFile()
必须以
“NotAllowedError”
DOMException
拒绝,其原因与针对同一哈希的并发
requestFileHandle()
调用相同(参见
§ 3.2 读取文件):即使请求该句柄的调用方,也不得把尚未验证的占位条目当作文件的真实内容来观察。
一旦 entry 的状态为“written”,在这类句柄上调用 getFile()
就会返回一个 File,
其内容为 entry 的字节;这正是 [FS]
已经为一个文件
条目定义的行为,该条目的二进制数据是
entry 的字节。
5. 存储管理
5.1. 存储限制
用户代理必须限制一个源通过成功写入可以向
COS
注册表贡献的总字节数,以防止单个源通过向缓存中灌入数据来试图逐出其他源的条目。具体限制
由实现定义。如果某个源的写入会超过其限制,用户代理必须
以“QuotaExceededError”
DOMException
拒绝该写入,并且应在
控制台中记录警告。
注:由于条目是内容寻址的,因此源在同一哈希下反复写入相同 字节时,不会在首次成功写入后继续消耗额外配额;参见 § 2.2 COS 条目。
用户代理还具有一个由实现定义的 最大源列表长度,它是一个正整数,用于限制单个源列表可以包含多少个源。该限制既会在首次提供列表时强制实施(参见验证 COS 请求),
也会在后来将列表合并到现有条目时强制实施(参见
升级资源可见性),这样,无论单次调用还是许多调用随时间累积产生的效果,都无法使条目的源无限增长。除了限制内存使用之外,这也防止源的列表被用作
未声明的“*”替代形式;参见§ 7.2 跨站探测。
在这两个位置中,超过该限制的处理方式不同,因为这两次调用发生在操作中截然不同的阶段:
-
在验证 COS 请求中,会在获取、哈希或写入任何文件之前检查该限制。超过限制的调用方会立即收到“
TypeError”, 并且不会发生其他任何事情——这与任何其他格式错误的origins值受到的处理相同。 -
在升级资源可见性中,只有不同源可能在很长一段时间内通过相互独立的写入调用累计请求源,才会达到该限制,而且只有在本次调用的字节已经经过哈希、验证并持久存储后才会检查该限制。此时拒绝写入会因为一个无关的记录维护限制而丢弃一次成功且已经验证的写入——而该写入可能非常大——并且甚至无法将问题归因于某个调用方的错误。因此,该写入仍会成功,只会从源中丢弃超过容量的源,并产生 控制台警告。这与现有对 从更宽松范围请求更受限范围的处理方式一致:无法完全满足的源请求会被静默限制,而不会使承载它的写入 失败。与本规范其他位置的§ 7.3 可用性门控保持一致, 调用方无法可靠地事后确认其请求的源是否 实际被添加:后续以该源身份调用
requestFileHandle()仍可能失败,即使该源已经添加——例如由于 GREASE 处理——因此其结果也不能被视为任何一种方向上的确认。
5.2. 逐出
本节为非规范性内容。
在存储空间紧张时,用户代理可以从 COS 注册表中逐出条目, 例如,可以针对最近访问过各条目的存储源使用最近最少使用策略。用户代理应提供设置界面,使用户可以检查存储了哪些文件、哪些源访问过每个文件,并手动删除条目或清除全部跨源存储数据。
当用户清除某个源的站点数据时,用户代理应将该源从它出现于其中的每个 存储 源集合中移除。如果移除后某个 COS 条目的 存储 源为空,则用户代理可以考虑删除该条目。
5.3. 手动添加的条目
本节为非规范性内容。
上文所述的设置界面可以允许用户将磁盘上已经拥有的文件直接添加到跨源存储中——例如独立于任何网站下载的 AI 模型——而无需任何脚本调用 requestFileHandle()。
这样的条目没有可用于归因写入的请求源,这会引出两个命令式
API 本身无法回答的问题:
-
就 § 3.4.1 原始存储方访问而言,谁是原始存储方?没有人:该条目的存储源最初为空, 而不是包含执行写入的某个源。因此,没有任何源可以免受其他所有请求源都需遵守的普通可用性门控和源检查——这是正确结果,因为实际上没有任何源写入这些字节。
-
源 是否应当像省略
origins的普通写入那样,默认为仅同站?不应当:“同站”只有相对于请求源才有意义,而手动添加没有请求源,因此不存在可供“同站”指代的站点。用户代理应当改为将这类条目的源默认设为“*”, 因为手动预置文件只有在其他源确实能够发现它时,才能达到其目的——让用户尚未访问的网站复用用户已经存放在磁盘上的文件。用户代理的设置界面还可以允许用户选择更窄的作用域。
以这种方式添加后,一个状态为“written”的条目,在其他方面与网站通过 requestFileHandle()
写入的条目无法区分:
对它适用完全相同的可用性门控和可见性
升级规则,就像对具有相同源值的任何其他条目一样——
包括在该条目最终作用域为“*”时执行位于 PHL
中检查;
这是手动添加的默认值,但不是唯一可能的结果。
6. 权限策略集成
本规范定义了一个由字符串“cross-origin-storage”标识的策略控制功能 [permissions-policy]。其默认许可列表
为 self。
不获准使用此功能的Document,会导致从该
Document(或所有者为该Document 的
worker)发出的每次
requestFileHandle()
调用,在执行任何哈希验证、注册表查找或写入之前,以“NotAllowedError”
DOMException
拒绝。
7. 隐私和安全注意事项
除使用 RFC 2119 关键词的地方外,本节均为非规范性内容;另请参见仓库中的 安全和隐私调查问卷。
7.1. 资源完整性
每个 COS 条目都以加密哈希为键,并根据该哈希验证其字节(参见
§ 3.3 创建和写入文件)。因此,网站可以确信,通过
requestFileHandle()
获得的文件,与自行获取 hash 时得到的文件具有完全相同的字节。开发者不能枚举跨源存储的内容,也不能在事先不知道文件哈希的情况下访问文件。
COS 哈希不是秘密。它是内容标识符,任何已经拥有文件的人都可以轻易计算出它,并且对于本规范所针对的广泛分发资源而言,它通常是公开信息——例如,与模型或库的版本一起发布。因此,知道哈希足以查找条目,但与能力 URL 或持有者令牌不同,它本身不会授予任何访问权限:查找是否成功完全由源和§ 7.3 可用性门控控制,而绝不取决于该哈希是否碰巧未公开。开发者不得将未公开的哈希视为访问控制机制;对特定文件感兴趣的攻击者无需猜测其哈希,只需获得文件本身即可(参见§ 7.2 跨站探测)。
7.2. 跨站探测
如果某个资源只被少量网站使用,能够得知该资源存在于跨源存储中的攻击者,就可以推断用户可能访问过其中某个网站。由于
requestFileHandle()
是了解资源是否存在的机制,因此每次调用实际上都是一次探测。本规范中的两个独立机制限制了探测能够了解的信息:
-
源(参见 § 3.4 资源可见性升级)允许存储源将 披露限制为选定的一组源,因此专有资源或低流行度资源根本不需要在全局范围内可探测。
-
§ 7.3 可用性门控还会阻止将作用域为 “
*”的资源向其存储源之外的请求源披露,除非该哈希位于 PHL 中; 因此,以origins: “*”存储的资源不会自动变得可被 Web 上的每个源探测。此门控仅适用于 作用域为“*”的条目;对于列表作用域和同站作用域条目,第一项中的限制是唯一门控,因为存储源已经明确限制了这些条目的披露范围。
只有在“选定的一组源”确实明显小于整个 Web 时,第一项才成立。
origins
的结构本身无法阻止调用方列举大量源——例如,根据公开的热门网站排名构建一个列表——这在功能上会近似全局披露,同时绕过只有“*”才要求的刻意、明确选择加入。最大源列表
长度(参见
§ 5.1 存储限制)对此进行了限制:该限制应足够大,以适应真正的多属性使用场景
(由共同控制的一小组相关源),但又应远不足以有意义地近似“每个源”,从而防止源的受限源形式被用作
未声明的“*”替代形式。
用户代理还应对单个源反复调用
requestFileHandle()
进行速率限制或以其他方式进行节流,并且可以应用设备端启发式方法(例如检测针对看起来是为每个用户单独生成的哈希的请求模式)来识别和阻止探测尝试,而不受被探测的哈希是否位于 PHL 中的影响。
7.3. 可用性门控
来自存储
源之外某个源的 requestFileHandle()
读取是否成功,始终取决于请求源是否获准读取该条目,而这由源控制。对于作用域为“*”的条目,还适用第二个独立问题:用户代理是否愿意披露该条目确实存在,这由PHL
成员资格和GREASE 处理控制。对于此类条目,两者都必须成立;对于列表作用域或同站作用域条目,仅适用第一个问题,
尽管 GREASE 处理仍可能抑制本来会被源允许的披露。规范性算法参见§ 3.2 读取文件。因此,“NotFoundError”
并不能证明资源不存在:它可能意味着资源不存在、请求源超出作用域、作用域为“*”的资源的哈希尚未(或永远不会)位于 PHL 中,或者 GREASE
处理抑制了一个真正的肯定结果。开发者不得将“NotFoundError”
解释为比“回退到网络”更具体的含义。
7.4. GREASE 处理
用户代理可以应用 GREASE 处理(生成 随机扩展并保持 可扩展性):即使§ 7.3 可用性门控本来允许披露,也偶尔表现得像某个可披露条目不存在一样。这会添加噪声,使网站更难区分真正不存在与出于隐私目的产生的假阴性,类似于 UA 客户端 提示所采用的技术。
应用 GREASE 处理的用户代理必须根据所涉条目的大小作出适度判断。对于小型条目,回退到网络获取的成本很低,因此偶尔出现假阴性是一种合理的隐私权衡。用户代理不得对体积大到无意义的重新下载明显与其隐私收益不成比例的条目应用 GREASE 处理——例如,GB 级 AI 模型权重——因为对这类条目产生假阴性会迫使执行一次完整、可观察且成本高昂的重新下载,而不是廉价的重新下载。
7.5. 指纹识别
攻击者通过探测跨源存储能够提取的信息量,取决于被探测资源的流行程度。得知用户拥有非常流行的资源,例如常见 AI 模型或广泛使用的 JavaScript 库,只能说明用户访问过使用该资源的众多网站之一。得知用户拥有罕见或唯一的资源则更具信息量。用户代理应应用设备端启发式方法,检测其哈希看起来是刻意为每个用户唯一生成的资源(例如,仅由单个站点写入过的哈希,或以异常高的频率被请求的哈希),并将这类模式视为探测尝试,而不受名义上的 PHL 状态影响。
8. 与其他规范的集成
本节为非规范性内容。
这里定义的 COS 注册表
还可以从三种宿主语言中通过声明式方式访问,而无需直接经过
requestFileHandle()。
每种集成都在其宿主语言的规范中定义,而不是在本文档中定义;本规范仅定义这些集成预计将基于的共享底层概念(COS 哈希、COS
条目、
可用性门控和公共哈希列表)。
-
HTML:在已经带有
integrity[SRI] 的link和script元素上使用crossoriginstorage属性,并将其提议纳入 WHATWG HTML 标准。 -
JavaScript:一个由宿主定义的
crossOriginStorage导入属性,可与integrity一起使用,建立在导入属性 提案之上,并将其提议纳入 WHATWG HTML 标准。 -
CSS:一个
cross-origin-storage()<request-url-modifier>, 可与integrity()一起使用,并已通过 w3c/csswg-drafts#14056 向 CSS 工作组提出。
仅作为说明,下面展示了同一个全局共享资源如何通过这三种形式分别选择加入跨源存储:
< script src = "popular-library.js" integrity = "sha256-abc123..." crossoriginstorage = "*" ></ script >
import datafrom "popular-resource.ext" with { integrity: "sha256-abc123..." , crossOriginStorage: "*" , };
@font-face { font-family : "Popular Font" ; src : url ( "popular-font.woff2" integrity("sha256-abc123..." ) cross-origin-storage ( *)); }
这些代码片段仅用于说明。本规范没有定义这些语法,每种形式的权威语法——包括受限、非“*”源值的写法——属于其各自宿主语言的规范,而不属于本文档。
预计这三种形式都会共享以下处理模型:首先查询 COS
注册表以寻找匹配且可披露的条目,
然后再回退到网络获取;并将成功获取且通过完整性验证的资源存储到 COS
注册表中,以供其他源复用,
这与 requestFileHandle()
的行为完全相同。有关这些集成的讨论应当在承载它们的宿主语言规范的跟踪器中进行,而不是在本仓库的问题跟踪器中进行。
致谢
非常感谢 Tab Atkins-Bittner、Yash Raj Bharti 和 Joshua Lochner 提供的宝贵反馈,以及 Kenji Baheux 和 Kevin Moore 提供的宝贵启发或想法。
本规范包含仿照 文件系统编写的材料,该材料依据 W3C 软件 和文档许可证提供。