From 4989e202498ddab59645f64b26bd3555ddd18f43 Mon Sep 17 00:00:00 2001 From: Camillia Smith Barnes Date: Wed, 12 Jun 2024 13:01:53 -0400 Subject: [PATCH 1/4] [Explainer] Allow cross-origin script in `addModule` & align `createWorklet` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, `addModule` only allows same-origin script. This was for convenience of the initial implementation, however, and is no longer necessary. The [worklet standard](https://html.spec.whatwg.org/multipage/worklets.html#dom-worklet-addmodule) does not contain this restriction. In fact, we have received [feedback](https://github.com/WICG/shared-storage/issues/127) from developers stating they would like to be able to host and run their worklet script on a separate origin---say a CDN---from the origin that owns and writes their shared storage data. So we update the explainer to remove the same-origin restriction for `addModule`. We also note that, when the worklet script is cross-origin to the invoking context, the invoking context's origin is used as the partition origin for accessing shared storage. Since `createWorklet` already allows cross-origin scripts, but currently uses the worklet script's origin as the data partition origin, updating `addModule` as described above without also making a change to `createWorklet` is liable to cause developer confusion in the long term. We have therefore decided to align `createWorklet`'s default data partition origin with `addModule`'s. `createWorklet` will use the invoking context's origin by default. This is a breaking change, but current usage of `createWorklet` is low as it was just introduced in M125. To preserve the ability to create a worklet whose script is cross-origin to the invoking context and then run operations on the worklet script origin's shared storage data, we introduce a new `dataOrigin` option for `createWorklet`. Passing `dataOrigin` with "script-origin" as its value will designate the worklet script origin as the partition origin for shared storage. For now, "script-origin" and "context-origin" will be the only allowed values for `dataOrigin`, when used. We’re considering adding support for separate data and script origins for `createWorklet` in the future. A corresponding specification update will follow. --- README.md | 49 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a67b46a..873b146 100644 --- a/README.md +++ b/README.md @@ -123,8 +123,10 @@ The shared storage worklet invocation methods (`addModule`, `run`, and `selectUR * Deletes all entries. * `window.sharedStorage.worklet.addModule(url, options)` * Loads and adds the module to the worklet (i.e. for registering operations). The handling should follow the [worklet standard](https://html.spec.whatwg.org/multipage/worklets.html#dom-worklet-addmodule), unless clarified otherwise below. - * This method can only be invoked once per worklet. This is because after the initial script loading, shared storage data will be made accessible inside the worklet environment, which can be leaked via subsequent `addModule()` (e.g. via timing). - * `url`'s origin must match that of the context that invoked `addModule(url)`. + * This method can only be invoked once per worklet. This is because after the initial script loading, shared storage data (for the invoking origin) will be made accessible inside the worklet environment, which can be leaked via subsequent `addModule()` (e.g. via timing). + * `url`'s origin need not match that of the context that invoked `addModule(url)`. + * If `url` is cross-origin to the invoking context, the worklet will use the invoking context's origin as its partition origin for accessing shared storage data and for budget checking and withdrawing. + * Also, for a cross-origin`url`, the CORS protocol applies. * Redirects are not allowed. * `window.sharedStorage.worklet.run(name, options)`, \ `window.sharedStorage.worklet.selectURL(name, urls, options)`, … @@ -147,10 +149,12 @@ The shared storage worklet invocation methods (`addModule`, `run`, and `selectUR * The behavior is identical to `window.sharedStorage.worklet.run(name, options)` and `window.sharedStorage.worklet.selectURL(name, urls, options)`. * `window.sharedStorage.createWorklet(url, options)` * Creates a new worklet, and loads and adds the module to the worklet (similar to the handling for `window.sharedStorage.worklet.addModule(url, options)`). - * The worklet uses the `url`'s origin as its partition origin for accessing shared storage data and for budget checking and withdrawing. + * By default, the worklet uses the invoking context's origin as its partition origin for accessing shared storage data and for budget checking and withdrawing. + * To instead use the worklet script origin (i.e. `url`'s origin) as the partition origin for accessing shared storage, pass the `dataOrigin` option with "script-origin" as its value in the `options` dictionary. + * Currently, the `dataOrigin` option, if used, is restricted to having either "script-origin" or "context-origin" as its value. "script-origin" designates the worklet script origin as the data partition origin; "context-origin" designates the invoking context origin as the data partition origin. * The object that the returned Promise resolves to has the same type with the implicitly constructed `window.sharedStorage.worklet`. However, for a worklet created via `window.sharedStorage.createWorklet(url, options)`, only `selectURL()` and `run()` are available, whereas calling `addModule()` will throw an error. This is to prevent leaking shared storage data via `addModule()`, similar to the reason why `addModule()` can only be invoked once on the implicitly constructed `window.sharedStorage.worklet`. * Redirects are not allowed. - * When the module script's URL's origin is cross-origin with the worklet's creator window's origin, a `Shared-Storage-Cross-Origin-Worklet-Allowed: ?1` response header is required. + * When the module script's URL's origin is cross-origin with the worklet's creator window's origin and the `dataOrigin` option is different from the context origin, a `Shared-Storage-Cross-Origin-Worklet-Allowed: ?1` response header is required. * The script server must carefully consider the security risks of allowing worklet creation by other origins (via `Shared-Storage-Cross-Origin-Worklet-Allowed: ?1` and CORS), because this will also allow the worklet creator to run subsequent operations, and a malicious actor could poison and use up the worklet origin's budget. @@ -455,6 +459,41 @@ register('select-url', URLOperation); register('report', ReportOperation); ``` +### Using cross-origin worklets + +There are currently three (3) different approaches to creating a worklet with cross-origin script. + + +1. Call `addModule()` with a cross-origin script. + + In an "https://a.example" context in the embedder page: + + ``` + await sharedStorage.addModule("https://b.example/worklet.js"); + ``` + + For any subsequent `run()` or `selectURL()` operation invoked on this worklet, the shared storage data for "https://a.example" (i.e. the context origin) will be used. + +2. Call `createWorklet()` with a cross-origin script. + + In an "https://a.example" context in the embedder page: + + ``` + const worklet = await sharedStorage.createWorklet("https://b.example/worklet.js"); + ``` + + For any subsequent `run()` or `selectURL()` operation invoked on this worklet, the shared storage data for "https://a.example" (i.e. the context origin) will be used. + +3. Call `createWorklet()` with a cross-origin script, setting its `dataOption` to the worklet script's origin. + + In an "https://a.example" context in the embedder page: + + ``` + const worklet = await sharedStorage.createWorklet("https://b.example/worklet.js", {dataOrigin: "script-origin"}); + ``` + + For any subsequent `run()` or `selectURL()` operation invoked on this worklet, the shared storage data for "https://b.example" (i.e. the worklet script origin) will be used. + ### Writing to Shared Storage via response headers For an origin making changes to their Shared Storage data at a point when they do not need to read the data, an alternative to using the Shared Storage JavaScript API is to trigger setter and/or deleter operations via the HTTP response header `Shared-Storage-Write` as in the examples below. @@ -507,7 +546,7 @@ The sharedStorage.selectURL() method can be disallowed by the "shared-storage-se ### Permissions Policy inside the shared storage worklet The permissions policy inside the shared storage worklet will inherit the permissions policy of the associated document. -The [Private Aggregation API](https://github.com/patcg-individual-drafts/private-aggregation-api) will be controlled by the "private-aggregation" policy-controlled feature: within the shared storage worklet, if the "private-aggregation" policy-controlled feature is disabled, the `privateAggregation` methods will throw an exception. +The [Private Aggregation API](https://github.com/patcg-individual-drafts/private-aggregation-api) will be controlled by the "private-aggregation" policy-controlled feature: within the shared storage worklet, if the "private-aggregation" policy-controlled feature is disabled, the `privateAggregation` methods will throw an exception. ## Data Retention Policy Each key is cleared after thirty days of last write (`set` or `append` call). If `ignoreIfPresent` is true, the last write time is updated. From f69a8c4cb87ab37accdc5d8b93267438fb020b60 Mon Sep 17 00:00:00 2001 From: Camillia Smith Barnes Date: Thu, 13 Jun 2024 11:42:00 -0400 Subject: [PATCH 2/4] Rephrase examples --- README.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 873b146..b7cdaaa 100644 --- a/README.md +++ b/README.md @@ -459,9 +459,9 @@ register('select-url', URLOperation); register('report', ReportOperation); ``` -### Using cross-origin worklets +### Loading cross-origin worklet scripts -There are currently three (3) different approaches to creating a worklet with cross-origin script. +There are currently four (4) approaches to creating a worklet that loads cross-origin script. The partition origin for the worklet's shared storage data access depends on the approach. 1. Call `addModule()` with a cross-origin script. @@ -484,7 +484,17 @@ There are currently three (3) different approaches to creating a worklet with cr For any subsequent `run()` or `selectURL()` operation invoked on this worklet, the shared storage data for "https://a.example" (i.e. the context origin) will be used. -3. Call `createWorklet()` with a cross-origin script, setting its `dataOption` to the worklet script's origin. +3. Call `createWorklet()` with a cross-origin script, setting its `dataOption` to the invoking context's origin. + + In an "https://a.example" context in the embedder page: + + ``` + const worklet = await sharedStorage.createWorklet("https://b.example/worklet.js", {dataOrigin: "context-origin"}); + ``` + + For any subsequent `run()` or `selectURL()` operation invoked on this worklet, the shared storage data for "https://a.example" (i.e. the context origin) will be used. + +4. Call `createWorklet()` with a cross-origin script, setting its `dataOption` to the worklet script's origin. In an "https://a.example" context in the embedder page: From 667898722f956fc84e8694adc4bebaa12679bd1f Mon Sep 17 00:00:00 2001 From: Camillia Smith Barnes Date: Thu, 13 Jun 2024 12:00:44 -0400 Subject: [PATCH 3/4] Further revise examples --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index b7cdaaa..f213a3a 100644 --- a/README.md +++ b/README.md @@ -463,6 +463,8 @@ register('report', ReportOperation); There are currently four (4) approaches to creating a worklet that loads cross-origin script. The partition origin for the worklet's shared storage data access depends on the approach. +#### Using the context origin as data partition origin +The first three (3) approaches use the invoking context's origin as the partition origin for shared storage data access and the invoking context's site for shared storage budget withdrawals. 1. Call `addModule()` with a cross-origin script. @@ -494,6 +496,9 @@ There are currently four (4) approaches to creating a worklet that loads cross-o For any subsequent `run()` or `selectURL()` operation invoked on this worklet, the shared storage data for "https://a.example" (i.e. the context origin) will be used. +#### Using the worklet script origin as data partition origin +The fourth approach uses the worklet script's origin as the partition origin for shared storage data access and the worklet script's site for shared storage budget withdrawals. + 4. Call `createWorklet()` with a cross-origin script, setting its `dataOption` to the worklet script's origin. In an "https://a.example" context in the embedder page: From c3e1c237511bbcb78e5b9850d23ddcf1e6c1f075 Mon Sep 17 00:00:00 2001 From: Camillia Smith Barnes Date: Fri, 14 Jun 2024 17:50:44 -0400 Subject: [PATCH 4/4] Address xyaoinum's comments --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f213a3a..a17e1f7 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ The shared storage worklet invocation methods (`addModule`, `run`, and `selectUR * Currently, the `dataOrigin` option, if used, is restricted to having either "script-origin" or "context-origin" as its value. "script-origin" designates the worklet script origin as the data partition origin; "context-origin" designates the invoking context origin as the data partition origin. * The object that the returned Promise resolves to has the same type with the implicitly constructed `window.sharedStorage.worklet`. However, for a worklet created via `window.sharedStorage.createWorklet(url, options)`, only `selectURL()` and `run()` are available, whereas calling `addModule()` will throw an error. This is to prevent leaking shared storage data via `addModule()`, similar to the reason why `addModule()` can only be invoked once on the implicitly constructed `window.sharedStorage.worklet`. * Redirects are not allowed. - * When the module script's URL's origin is cross-origin with the worklet's creator window's origin and the `dataOrigin` option is different from the context origin, a `Shared-Storage-Cross-Origin-Worklet-Allowed: ?1` response header is required. + * When the module script's URL's origin is cross-origin with the worklet's creator window's origin and when `dataOrigin` is "script-origin", a `Shared-Storage-Cross-Origin-Worklet-Allowed: ?1` response header is required. * The script server must carefully consider the security risks of allowing worklet creation by other origins (via `Shared-Storage-Cross-Origin-Worklet-Allowed: ?1` and CORS), because this will also allow the worklet creator to run subsequent operations, and a malicious actor could poison and use up the worklet origin's budget. @@ -471,7 +471,7 @@ The first three (3) approaches use the invoking context's origin as the partitio In an "https://a.example" context in the embedder page: ``` - await sharedStorage.addModule("https://b.example/worklet.js"); + await sharedStorage.worklet.addModule("https://b.example/worklet.js"); ``` For any subsequent `run()` or `selectURL()` operation invoked on this worklet, the shared storage data for "https://a.example" (i.e. the context origin) will be used.