跨源存储

社区组报告草案

此版本:
https://wicg.github.io/cross-origin-storage/
问题跟踪:
GitHub
规范内问题
编辑:
Google
Thinktecture AG
Google
参与:
GitHub WICG/cross-origin-storage新建问题未解决的问题
提交:
GitHub index.bs 提交记录

摘要

跨源存储(COS)是一种内容寻址缓存,允许 Web 应用跨不同源存储和检索大型文件,例如 AI 模型、WebAssembly 模块和广泛使用的 JavaScript 库。文件通过其加密哈希而非 URL 进行标识,因此,某个源获取过一次的字节完全相同的资源,可以由用户代理允许的任何其他源复用,而无需再次下载。为了防止通过缓存中是否存在某个文件来推断用户访问过哪些网站,只有其哈希达到类似 k-匿名的流行度门槛时,才允许跨源披露该文件。

本文档的状态

本规范由 Web 平台孵化器社区组发布。 它既不是 W3C 标准,也不处于 W3C 标准流程中。 请注意,根据 W3C 社区贡献者许可协议 (CLA), 仅提供有限的退出选择,并且还适用其他条件。 详细了解 W3C 社区组和业务组

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]中的 FileSystemFileHandleFileSystemWritableFileStream 以及相关基础设施,但其作用域是一个专用的、跨源共享的文件系统,而不是单个源的私有文件系统或用户可见文件系统。

本规范仅定义命令式 JavaScript API。相关提案将同一个底层缓存与声明式标记集成——HTML crossoriginstorage 属性作用于 linkscript, JavaScript 的 crossOriginStorage 导入属性,以及 CSS cross-origin-storage() <request-url-modifier>——每一种都在其宿主语言自己的规范中定义;参见§ 8 与其他规范的 集成

2. 概念

2.1. 哈希

COS 哈希是一个结构,具有以下

算法

一个字符串, 用于命名 [WEBCRYPTO] 所识别的哈希算法,例如“SHA-256”。

一个字符串,它是由算法生成的摘要的小写十六进制编码。 对于“SHA-256”,其长度为 64 个字符。

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 哈希

字节

Null,或一个字节序列,其在哈希算法 下计算出的摘要等于哈希。在写入方完成 验证和 存储之前为 Null。

状态

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;

每个 NavigatorWorkerNavigator 对象都具有关联的 CrossOriginStorageManager 对象。crossOriginStorage 获取器 的步骤是返回this 的关联 CrossOriginStorageManager

每个 CrossOriginStorageManager 对象都具有关联的,在创建该对象时,将其设置为 this相关设置对象

3.1. requestFileHandle() 方法

handle = await navigator . crossOriginStorage . requestFileHandle(hash)

如果由 hash 标识的文件存在于跨源存储中,并且可向调用源披露,则返回该文件的句柄。否则,以 “NotFoundErrorDOMException 拒绝。出现“NotFoundError” 并不能证明该文件在物理上不存在于跨源存储中;参见§ 7.3 可用性门控。调用方可以将其视为“改为从网络获取此文件”。

handle = await navigator . crossOriginStorage . requestFileHandle(hash, { create: true })

返回一个可用于将由 hash 标识的文件写入跨源存储的句柄;如果尚不存在条目,则创建一个新条目,默认仅限同站源。 无论条目是否已经存在,调用方都需要通过该句柄的 createWritable() 方法写入完整文件内容;此时用户代理会验证写入的字节是否哈希为 hash, 否则以“DataErrorDOMException 拒绝。此要求可防止源将创建请求用作判断 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) 方法的步骤如下:
  1. result一个新的 promise

  2. realmthis相关 Realm

  3. globalthis相关全局对象

  4. originthis 的关联

  5. 如果 global 的关联 Document (若有)不获准使用cross-origin-storage策略控制功能,则:

    1. 在给定 global 的情况下,将一个全局任务排入队列,该任务位于DOM 操作任务源上,用“NotAllowedErrorDOMException 拒绝 result

    2. 返回 result

  6. validationFailure 为在给定 hashoptions 的情况下运行验证 COS 请求所得的结果。

  7. 如果 validationFailure 不为 null:

    1. 在给定 global 的情况下,将一个全局任务排入队列,该任务位于DOM 操作任务源上,以 validationFailure 拒绝 result

    2. 返回 result

  8. 将以下步骤入队跨源存储队列

    1. 如果 options["create"] 为 true:

      1. 在给定 resulthashoptionsglobalrealm 的情况下,运行完成创建请求

    2. 否则:

      1. 在给定 resulthashoriginglobalrealm 的情况下,运行完成读取请求

  9. 返回 result

要在给定 CrossOriginStorageRequestFileHandleHash hashCrossOriginStorageRequestFileHandleOptions options 的情况下验证 COS 请求,返回 null 或 TypeError
  1. 如果 hash["algorithm"] 不是 [WEBCRYPTO] 所识别的哈希算法 名称,则返回一个新的 TypeError

  2. hash["algorithm"] 以 ASCII 不区分大小写的方式 匹配“SHA-256”时,如果 hash["value"] 不匹配正则表达式 /^[0-9a-f]{64}$/,则返回一个新的 TypeError

    注:未来的哈希算法可以定义不同的预期摘要长度;本规范仅对“SHA-256”施加规范性约束,与其在各个示例中的用法一致。

  3. 如果 options["origins"] 存在且不为 “*”:

    1. candidatesoptions["origins"], 并将单个字符串视为仅含一个元素的列表

    2. 如果 candidates大小大于用户代理的 最大源列表长度,则返回一个新的 TypeError

    3. 对于 candidates 中的每个 candidate

      1. 如果在 candidate 上运行基本 URL 解析器所得的结果为失败,或者解析后的 URL 的不透明源,则返回一个新的 TypeError

  4. 返回 null。

3.2. 读取文件

要在给定一个 promise result、一个 COS 哈希 hash、一个 origin、一个全局对象 global 和一个 Realm realm 的情况下完成读取 请求
  1. entryCOS 注册表哈希hash 相等COS 条目(若有),否则为 null。

  2. 如果 entry 不为 null,并且 entry状态为“pending”:

    1. 在给定 global 的情况下,将一个全局任务排入队列,该任务位于DOM 操作任务源上,用“NotAllowedErrorDOMException 拒绝 result

    2. 返回。

    注:正在写入的哈希会被刻意处理为与不存在的哈希不同,以防调用方将进行中的写入误认为真正的缓存未命中,并仅为写入该文件而开始重复、并发下载一个非常大的文件。参见§ 3.3 创建和写入文件

  3. disclosableEntry 为在给定 entryorigin 的情况下运行应用可用性 门控所得的结果。

  4. 如果 disclosableEntry 为 null:

    1. 在给定 global 的情况下,将一个全局任务排入队列,该任务位于DOM 操作任务源上,用“NotFoundErrorDOMException 拒绝 result

    2. 返回。

  5. handle创建一个新的 FileSystemFileHandle [FS]所得的结果,其 定位器跨源存储文件系统内寻址 disclosableEntry,并位于 realm 中。

  6. 在给定 global 的情况下,将一个全局任务排入队列,该任务位于DOM 操作任务源上,以 handle 兑现 result

要在给定一个 COS 条目 或 null entry,以及一个 origin 的情况下确定 COS 披露,返回一个 COS 条目或 null:
  1. 如果 entry 为 null,则返回 null。

  2. 断言entry状态为“written”。

  3. 如果 origin 位于 entry存储 源中,则返回 entry

    注:原始存储方以及任何已成功写入过该条目的源始终可以重新读取该条目。参见§ 3.4.1 原始存储方 访问

  4. 如果 entry为“*”:

    1. 如果 entry哈希位于 PHL 中,则返回 null。

      注:公共哈希列表门控仅在此处应用于全局作用域的条目——这是披露原本可能触及 Web 上任何源的唯一情况。它不适用于下面的列表作用域和同站作用域情况:对于这些情况,存储源已经作出了明确且有界的披露决定,如果还要求额外的全局普遍性,就会使普通的受限共享(参见§ 3.4 资源可见性升级)依赖于 对通常属于专有资源的内容进行不相关的公共策展。参见 § 7.2 跨站探测

    2. 返回 entry

  5. 如果 entry为一个列表

    1. 如果 origin序列化是该 列表中的一项, 则返回 entry

    2. 返回 null。

  6. 断言entry为 null。

  7. 如果 originentry存储源中的某一项同站,则返回 entry

  8. 返回 null。

要在给定一个 COS 条目或 null entry,以及一个 origin 的情况下应用 可用性门控,返回一个 COS 条目或 null:
  1. disclosable 为在给定 entryorigin 的情况下运行确定 COS 披露所得的结果。

  2. 如果 disclosable 为 null,则返回 null。

  3. 如果 origin 位于 disclosable存储 源中,则返回 disclosable

  4. 如果用户代理选择对该请求应用GREASE 处理,则返回 null。

  5. 返回 disclosable

3.3. 创建和写入文件

完成创建请求所创建的 FileSystemFileHandle 具有关联的 请求的源,其值是验证并存储在通过该句柄成功写入后,将尝试把条目的升级到的值。

要在给定一个 promise result、一个 COS 哈希 hash、一个 CrossOriginStorageRequestFileHandleOptions options、一个全局对象 global 和一个 Realm realm 的情况下完成 创建请求
  1. requestedOrigins 为在给定 options["origins"] 的情况下运行规范化 请求的源所得的结果。

  2. entryCOS 注册表哈希hash 相等COS 条目(若有),否则为 null。

  3. 如果 entry 为 null:

    1. entry 设置为一个新的 COS 条目,其哈希hash字节 为 null,状态为“pending”, requestedOrigins,并且 存储源为空集合

    2. COS 注册表[hash] 设置entry

  4. handle创建一个新的 FileSystemFileHandle [FS]所得的结果,其 定位器跨源存储文件系统内寻址 entry,并位于 realm 中。

  5. handle请求的源设置为 requestedOrigins

    注:无论 entry 是否已经存在,也无论它是否已经为“written”,都会返回 handle。调用方仍需要通过 handle 提供完整的文件字节,这样写入操作便无法用于检测先前是否存在该文件(参见 § 7.2 跨站探测),并且可以在接受对更宽松 值的请求之前进行验证;参见 § 3.4 资源可见性升级

  6. 在给定 global 的情况下,将一个全局任务排入队列,该任务位于DOM 操作任务源上,以 handle 兑现 result

要在给定一个 (DOMString or sequence<DOMString>) 或 undefined origins 的情况下规范化 请求的源,返回“*”、一个由 组成的列表,或 null:
  1. 如果 origins存在,则返回 null。

  2. 如果 origins 为“*”,则返回“*”。

  3. list 为 « »。

  4. 对于 origins 中的每个 candidate(单个 字符串被视为仅含一个元素的列表):

    1. candidateOrigin 为在 candidate 上运行基本 URL 解析器 所得的

      注:验证 COS 请求已经确认每个 candidate 都会解析为非不透明源。

    2. 如果 list包含 candidateOrigin,则将 candidateOrigin 追加list

  5. 返回 list

注:在此处进行去重,而不是仅在稍后合并 到现有条目时才去重,可使从条目首次创建的那一刻起就不含重复项,因此以后对它的每次克隆也都不会包含重复项。

当通过对其定位器 寻址某个 COS 条目的句柄调用 createWritable() 所获得的 FileSystemWritableFileStream 被关闭时 [FS],用户代理必须在该关闭操作的 promise 被兑现之前运行下面的验证并存储 步骤。

注:无论关闭是如何触发的,这都适用——可以是显式调用 close(), 也可以是 pipeTo() 调用到达其源的末尾,而后者默认也会关闭其目标。在流变为关闭状态时,[STREAMS] 不区分这两种情况,本算法同样不区分。

在给定已关闭流的句柄所寻址的 COS 条目 entry、完整写入的字节序列 bytes,以及关闭操作的 相关设置对象 origin 的情况下,验证并存储 的步骤如下:
  1. computedValue 为使用 entry算法所命名的算法,依照 [WEBCRYPTO]bytes 计算得到的小写十六进制摘要。

  2. 如果 computedValueentry不完全相等:

    1. 用“DataErrorDOMException 拒绝关闭操作的 promise,并保持 entry 不变。

    2. 中止这些步骤。

  3. 将以下步骤入队跨源存储队列

    1. entry字节设置为 bytes

    2. entry状态设置为“written”。

    3. 如果 origin 尚不存在,则将其追加entry存储源

    4. 在给定 entry 和句柄的 请求的源的情况下,运行升级资源可见性

文件系统标准 [FS] 尚未定义一个扩展点,使依赖它的规范能够将额外的逐次写入验证挂接到 FileSystemWritableFileStream 的关闭步骤中。在这种挂接机制出现之前,本节直接描述所需行为;预计未来的修订版会与 [FS] 进行正式集成。

3.4. 资源可见性升级

COS 条目的可见性可以升级,但绝不能降级。

要在给定一个 COS 条目 entry 和“*”、一个由 组成的列表,或 null requestedOrigins 的情况下升级 资源可见性
  1. 如果 requestedOrigins 为 null,则返回。

    注:省略 origins 绝不会缩小现有条目的范围;它只请求同站可用性,而每个条目已经至少具有同等程度的可用性。

  2. 如果 entry为“*”,则返回。

    注:已经全局可用的条目不能被后续写入方限制。当本注释适用且 requestedOrigins 不为“*”时,用户代理应在控制台中记录警告,告知开发者所请求的限制未被应用。

  3. 如果 requestedOrigins 为“*”:

    1. entry设置为“*”。

    2. 返回。

  4. 如果 entry为 null:

    1. entry设置为 requestedOrigins

    2. 返回。

  5. mergedentry克隆

  6. 对于 requestedOrigins 中的每个 candidateOrigin

    1. 如果 merged 包含 candidateOrigin,则继续。

    2. 如果 merged大小 等于用户代理的最大源列表长度,则 中断。

      注:任何剩余的 candidateOrigin 值都会从此次升级中静默丢弃,而不会使升级失败——因为写入本身已经成功——但用户代理应记录控制台警告,说明该条目的源列表已达到容量上限。

    3. candidateOrigin 追加merged

  7. entry设置为 merged

注:新网站——而不仅仅是原始存储方——也可以扩大条目的范围,只要它也提供能够哈希为该条目哈希的字节。这是有意为之: 任何已经拥有某个哈希的正确字节的网站,从构造上来说,都与原始存储方一样有权说明该哈希表示什么。

3.4.1. 原始存储方访问

成功完成过对某个 COS 条目写入——即该条目的存储源中的任何——始终可以随后通过 requestFileHandle() 获得其句柄,而不受该条目的 值影响,也不受其哈希是否位于 PHL 中的影响。 这与 Cache API 的模型一致:源始终能够访问自己存储的内容。

4. 跨源存储文件系统

跨源存储文件系统是一个文件系统根,不同于任何 源的存储桶文件系统或本地文件系统访问根;其 条目COS 注册表中的COS 条目项一一对应。

跨源存储文件系统不使用 [FS] 的普通逐次调用权限检查 模型:从中获得的每个 FileSystemFileHandle 在返回给脚本之前,都已经由 § 3.2 读取文件§ 3.3 创建和 写入文件完全授权,因此对这类句柄调用 getFile()createWritable() 绝不会触发额外的权限提示。

寻址某个 COS 条目 entryFileSystemFileHandle 可以在 entry状态仍为 “pending”时存在——例如,在 创建请求之后、相应写入完成之前。 对这类句柄调用 getFile() 必须以 “NotAllowedErrorDOMException 拒绝,其原因与针对同一哈希的并发 requestFileHandle() 调用相同(参见 § 3.2 读取文件):即使请求该句柄的调用方,也不得把尚未验证的占位条目当作文件的真实内容来观察。

一旦 entry状态为“written”,在这类句柄上调用 getFile() 就会返回一个 File, 其内容为 entry字节;这正是 [FS] 已经为一个文件 条目定义的行为,该条目的二进制数据entry字节

5. 存储管理

5.1. 存储限制

用户代理必须限制一个通过成功写入可以向 COS 注册表贡献的总字节数,以防止单个源通过向缓存中灌入数据来试图逐出其他源的条目。具体限制 由实现定义。如果某个源的写入会超过其限制,用户代理必须 以“QuotaExceededErrorDOMException 拒绝该写入,并且应在 控制台中记录警告。

注:由于条目是内容寻址的,因此源在同一哈希下反复写入相同 字节时,不会在首次成功写入后继续消耗额外配额;参见 § 2.2 COS 条目

用户代理还具有一个由实现定义最大源列表长度,它是一个正整数,用于限制单个列表可以包含多少个。该限制既会在首次提供列表时强制实施(参见验证 COS 请求), 也会在后来将列表合并到现有条目时强制实施(参见 升级资源可见性),这样,无论单次调用还是许多调用随时间累积产生的效果,都无法使条目的无限增长。除了限制内存使用之外,这也防止源的列表被用作 未声明的“*”替代形式;参见§ 7.2 跨站探测

在这两个位置中,超过该限制的处理方式不同,因为这两次调用发生在操作中截然不同的阶段:

5.2. 逐出

本节为非规范性内容。

在存储空间紧张时,用户代理可以从 COS 注册表中逐出条目, 例如,可以针对最近访问过各条目的存储源使用最近最少使用策略。用户代理应提供设置界面,使用户可以检查存储了哪些文件、哪些源访问过每个文件,并手动删除条目或清除全部跨源存储数据。

当用户清除某个源的站点数据时,用户代理应将该源从它出现于其中的每个 存储 源集合中移除。如果移除后某个 COS 条目存储 源为空,则用户代理可以考虑删除该条目。

5.3. 手动添加的条目

本节为非规范性内容。

上文所述的设置界面可以允许用户将磁盘上已经拥有的文件直接添加到跨源存储中——例如独立于任何网站下载的 AI 模型——而无需任何脚本调用 requestFileHandle()。 这样的条目没有可用于归因写入的请求,这会引出两个命令式 API 本身无法回答的问题:

以这种方式添加后,一个状态为“written”的条目,在其他方面与网站通过 requestFileHandle() 写入的条目无法区分: 对它适用完全相同的可用性门控可见性 升级规则,就像对具有相同值的任何其他条目一样—— 包括在该条目最终作用域为“*”时执行位于 PHL 中检查; 这是手动添加的默认值,但不是唯一可能的结果。

6. 权限策略集成

本规范定义了一个由字符串“cross-origin-storage”标识的策略控制功能 [permissions-policy]。其默认许可列表 为 self

获准使用此功能的Document,会导致从该 Document(或所有者为该Document 的 worker)发出的每次 requestFileHandle() 调用,在执行任何哈希验证、注册表查找或写入之前,以“NotAllowedErrorDOMException 拒绝。

7. 隐私和安全注意事项

除使用 RFC 2119 关键词的地方外,本节均为非规范性内容;另请参见仓库中的 安全和隐私调查问卷

7.1. 资源完整性

每个 COS 条目都以加密哈希为键,并根据该哈希验证其字节(参见 § 3.3 创建和写入文件)。因此,网站可以确信,通过 requestFileHandle() 获得的文件,与自行获取 hash 时得到的文件具有完全相同的字节。开发者不能枚举跨源存储的内容,也不能在事先不知道文件哈希的情况下访问文件。

COS 哈希不是秘密。它是内容标识符,任何已经拥有文件的人都可以轻易计算出它,并且对于本规范所针对的广泛分发资源而言,它通常是公开信息——例如,与模型或库的版本一起发布。因此,知道哈希足以查找条目,但与能力 URL 或持有者令牌不同,它本身不会授予任何访问权限:查找是否成功完全由§ 7.3 可用性门控控制,而绝不取决于该哈希是否碰巧未公开。开发者不得将未公开的哈希视为访问控制机制;对特定文件感兴趣的攻击者无需猜测其哈希,只需获得文件本身即可(参见§ 7.2 跨站探测)。

7.2. 跨站探测

如果某个资源只被少量网站使用,能够得知该资源存在于跨源存储中的攻击者,就可以推断用户可能访问过其中某个网站。由于 requestFileHandle() 是了解资源是否存在的机制,因此每次调用实际上都是一次探测。本规范中的两个独立机制限制了探测能够了解的信息:

只有在“选定的一组源”确实明显小于整个 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 条目可用性门控公共哈希列表)。

仅作为说明,下面展示了同一个全局共享资源如何通过这三种形式分别选择加入跨源存储:

<script src="popular-library.js" integrity="sha256-abc123..." crossoriginstorage="*"></script>
import data from "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 软件 和文档许可证提供。

一致性

文档 约定

一致性要求通过描述性断言 和 RFC 2119 术语的组合来表达。 在本文档的规范性部分中,关键词“MUST(必须)”、“MUST NOT(不得)”、“REQUIRED(要求)”、“SHALL(应)”、“SHALL NOT(不应)”、“SHOULD(应该)”、“SHOULD NOT(不应该)”、“RECOMMENDED(建议)”、 “MAY(可以)”和“OPTIONAL(可选)” 应按照 RFC 2119 中所述的方式解释。 不过,为了便于阅读, 这些词在本规范中并不全部以大写字母出现。

除明确标记为非规范性的章节、示例和注释外,本规范的所有文本均为规范性内容。[RFC2119]

本规范中的示例以“例如”等词引入, 或者使用 class="example" 与规范性文本分开, 如下所示:

这是一个信息性示例。

信息性注释以“注”一词开头, 并使用 class="note" 与规范性文本分开, 如下所示:

注:这是一条信息性注释。

索引

本规范定义的 术语

通过引用定义的 术语

参考文献

规范性参考文献

[DOM]
Anne van Kesteren。DOM 标准。现行标准。 URL:https://dom.spec.whatwg.org/
[ECMASCRIPT]
ECMAScript 语言规范。URL:https://tc39.es/ecma262/multipage/
[FileAPI]
Marijn Kruisselbrink。文件 API。URL:https://w3c.github.io/FileAPI/
[FS]
Austin Sullivan。文件系统标准。现行 标准。URL:https://fs.spec.whatwg.org/
[HTML]
Anne van Kesteren;等。HTML 标准。 现行标准。URL:https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren;Domenic Denicola。Infra 标准。现行标准。URL:https://infra.spec.whatwg.org/
[PERMISSIONS-POLICY]
Ian Clelland。权限 策略。URL:https://w3c.github.io/webappsec-permissions-policy/
[RFC2119]
S. Bradner。用于 RFC 中表示要求级别的 关键词。1997年3月。当前最佳实践。URL:https://datatracker.ietf.org/doc/html/rfc2119
[URL]
Anne van Kesteren。URL 标准。现行标准。 URL:https://url.spec.whatwg.org/
[WEBCRYPTO]
Daniel Huigens。Web 加密第 2 版。URL: https://w3c.github.io/webcrypto/
[WEBIDL]
Edgar Chen;Timothy Gu。Web IDL 标准。现行 标准。URL:https://webidl.spec.whatwg.org/

非规范性参考文献

[SRI]
Frederik Braun。子资源 完整性。URL:https://w3c.github.io/webappsec-subresource-integrity/
[STREAMS]
Adam Rice;等。流标准。现行 标准。URL:https://streams.spec.whatwg.org/
[UA-CLIENT-HINTS]
用户代理客户端提示。社区组报告草案。 URL:https://wicg.github.io/ua-client-hints/

IDL 索引

[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;

问题索引

公共哈希列表的检索协议、更新频率、数据格式、流行度阈值和治理方式 作为本规范的配套产物进行了详细设计,其理念类似于 [URL] 标准的公共后缀列表是一个配套数据文件,而非 规范性文本 — 请参阅本仓库中的 公共哈希列表说明文档。该设计 提议由 WHATWG 进行治理,参考公共后缀列表跨厂商、 滚动发布的先例,并以独立验证的普遍性作为准入标准 — 这是上述 k-匿名性风格门槛的一个具体实例,该门槛仅在哈希被纳入列表时离线应用一次, 而不是用户代理针对每次查询重复执行的检查。此方案目前尚未在 本提案之外建立。列表本身的一个早期、非规范性代码原型 维护在本仓库的 public-hash-list/implementation/ 中 — 这是一个务实的 临时存放位置;一个专用的跨厂商仓库仍然是 PHL 说明文档中描述的治理目标。 本规范仅依赖于某个哈希是否 在 PHL 中,而不依赖于任何特定的 检索机制或治理模型,因此无论该设计如何 演进,本规范都保持正确。
文件系统标准 [FS] 尚未为依赖规范定义一个扩展点,以便向 FileSystemWritableFileStream 的 关闭步骤中挂接额外的每次写入验证。在存在这样的挂接点之前,本节将直接描述所需行为;预计未来修订版将正式与 [FS] 集成。