From 205be9d236ce8ad465ca77b9e80603ff63f992a5 Mon Sep 17 00:00:00 2001 From: Aaron Selya Date: Fri, 7 Mar 2025 13:20:37 -0500 Subject: [PATCH 1/4] Update language to relpace CORS with SAH. --- index.bs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index 378669c..b7a8966 100644 --- a/index.bs +++ b/index.bs @@ -219,7 +219,7 @@ In [=http network or cache fetch=], when determining whether to block cookies, r 1. Let |has top-level access| be the result of running [=determine if a request has top-level storage access=] on |request|. 1. If |has top-level access| is false, return false. 1. Let |is subresource| be true if |request| is a [=subresource request=] and false otherwise. -1. Let |allowed subresource mode| be true if |request|'s [=request/mode=] is "cors" and |request|'s [=request/credentials mode=] is "include", and false otherwise. +1. Let |allowed subresource mode| be true if |request|'s [=request/storage access status=] is "[=storage access status/active=]", and false otherwise. 1. If |is subresource| is true and |allowed subresource mode| is false, return false. 1. If |request|'s [=request/client=]'s [=relevant global object=]'s [=associated document=] is not a [=traversable navigable=], return false. 1. Return true. @@ -277,7 +277,7 @@ For frame access, {{Document/requestStorageAccessFor(requestedOrigin)}} merely s The specific security controls proposed by the API are: * Any cookies included with the subresource request have to be explicitly marked `SameSite=None`, indicating intent for use in [=third party contexts=]. -* For any `SameSite=None` cookies to be included, the request's [=request/mode=] has to be "cors", where reading of the response is blocked unless the embeddee opts-in via sending the appropriate [:access-control-allow-credentials:] header. The sending of the [:origin:] header ensures the embeddee is aware of the embedder's identity. +* For any `SameSite=None` cookies to be included, the request's [=request/storage access status=] must be "[=storage access status/active=]". Additionally, only requests initiated from the top-level document will be eligible for inclusion of `SameSite=None` cookies. This ensures that other embedded frames do not receive escalated privileges. From 21b9f85c211a8cc8929363d2798d012014a8cc7c Mon Sep 17 00:00:00 2001 From: Aaron Selya Date: Mon, 10 Mar 2025 11:18:53 -0400 Subject: [PATCH 2/4] Update algorithm. Remove top-level-storage-access permissions content. --- index.bs | 169 +++++++++++++++++++------------------------------------ 1 file changed, 58 insertions(+), 111 deletions(-) diff --git a/index.bs b/index.bs index b7a8966..ea9cfdf 100644 --- a/index.bs +++ b/index.bs @@ -100,115 +100,77 @@ When invoked on {{Document}} |doc| with {{USVString}} |requestedOrigin|, the top-level-storage-access" and with {{TopLevelStorageAccessPermissionDescriptor/requestedOrigin}} set to |origin|. + +1. Let |embeddedOrigin| be |parsedURL|'s [=/origin=]. +1. If |embeddedOrigin| is an [=opaque origin=], [=reject=] |p| with an "{{NotAllowedError}}" {{DOMException}} and return |p|. +1. If |doc|'s [=Document/origin=] is [=same origin=] with |embeddedOrigin|, [=resolve=] and return |p|. 1. Let |has activation| be true if |doc|'s {{Window}} object has [=transient activation=], and false otherwise. -1. Run these steps [=in parallel=]: - 1. Let |settings| be |doc|'s [=relevant settings object=]. - 1. Let |global| be |doc|'s [=relevant global object=]. - 1. Let |existing state| be |descriptor|'s [=permission state=] with |settings|. - 1. If |existing state| is [=permission/granted=]: - 1. [=Queue a global task=] on the [=permissions task source=] given |global| to [=resolve=] |p|. - 1. Return. - 1. If |existing state| is [=permission/denied=]: - 1. If |doc|'s {{Window}} object has [=transient activation=], [=consume user activation=] with it. - 1. [=Queue a global task=] on the [=permissions task source=] given |global| to [=reject=] |p| with a "{{NotAllowedError}}" {{DOMException}}. - 1. Return. - 1. Assert that |doc|'s [=node navigable=] is a [=traversable navigable=]. - 1. If |has activation| is false: - 1. [=Queue a global task=] on the [=permissions task source=] given |global| to [=reject=] |p| with a n "{{NotAllowedError}}" {{DOMException}}. - 1. Return. - 1. Let |permissionState| be the result of [=requesting permission to use=] "top-level-storage-access" with |descriptor|. - - NOTE: Note that when requesting permissions and deciding whether to show a prompt, user agents apply implementation-defined behavior to shape the end user experience. Particularly for `top-level-storage-access`, user agents are known to apply custom rules that will grant or deny a permission without showing a prompt. - - 1. If |permissionState| is [=permission/granted=]: - 1. [=Queue a global task=] on the [=permissions task source=] given |global| to [=resolve=] |p|. - 1. Return. - 1. If |doc|'s {{Window}} object has [=transient activation=], [=consume user activation=] with it. - 1. [=Queue a global task=] on the [=permissions task source=] given |global| to [=reject=] |p| with a "{{NotAllowedError}}" {{DOMException}}. +1. Run the following steps [=in parallel=]: + 1. Let |process permission state| be an algorithm that, given a [=permission state=] |state|, runs the following steps: + 1. [=Queue a global task=] on the [=networking task source=] given |global| to: + 1. If |state| is [=permission/granted=]: + 1. Set |global|'s [=environment/has storage access=] to true. + 1. [=/Resolve=] |p| with {{undefined}}. + 1. Else: + 1. [=Consume user activation=] given |global|. + 1. [=/Reject=] |p| with a "{{NotAllowedError}}" {{DOMException}}. + 1. Let |explicitSetting| be the result of [=determine whether the user agent explicitly allows unpartitioned cookie access|determining whether the user agent explicitly allows unpartitioned cookie access=] with (|topLevelSite|, |embeddedSite|). + 1. If |explicitSetting| is "`disallow`": + 1. Run |process permission state| with [=permission/denied=]. + 1. Abort these steps. + 1. If |explicitSetting| is "`allow`": + 1. Run |process permission state| with [=permission/granted=]. + 1. Abort these steps. + 1. [=Assert=]: |explicitSetting| is "`none`". + 1. If |browsingContext| is a [=top-level browsing context=]: + 1. Run |process permission state| with [=permission/granted=]. + 1. Abort these steps. + 1. If |embeddedSite| is [=site/same site=] with |topLevelSite|: + + NOTE: This check is [=site/same site=] on purpose, to allow embedded sites to use `requestStorageAccess()` to opt into storage access without involvement from the end user in scenarios where storage access is restricted for security and not privacy purposes. + + 1. Run |process permission state| with [=permission/granted=]. + 1. Abort these steps. + 1. Let |previous permission state| be the result of [=getting the current permission state=] given "storage-access" and |global|. + 1. If |previous permission state| is not [=permission/prompt=]: + 1. Run |process permission state| with |previous permission state|. + 1. Abort these steps. + 1. Let |connected| be the result of [=Determine the effective FedCM connection status|determining the effective FedCM connection status=] given |topLevelOrigin|, |embeddedOrigin|, |doc|. + 1. If |connected|: + + NOTE: User agents are encouraged to keep track of which (site, site) tuples have been allowed to access storage due to existing FedCM connections, and double-check that list when accessing cookies to catch malicious attackers that have tricked an [=environment=] into using an incorrect [=environment/has storage access=] bit. + + 1. Run |process permission state| with [=permission/granted=]. + 1. Abort these steps. + 1. Let |permissionState| be the result of [=requesting permission to use=] "storage-access". + + NOTE: Note that when requesting permissions and deciding whether to show a prompt, user agents apply implementation-defined behavior to shape the end user experience. Particularly for `storage-access`, user agents are known to apply custom rules that will grant or deny a permission without showing a prompt. + + 1. Run |process permission state| with |permissionState|. 1. Return |p|. -ISSUE(privacycg/requestStorageAccessFor#15): The permissions task source shouldn't be used directly. - - - -

User Agent top-level storage access policies

- -
-To determine if a request has top-level storage access with [=request=] |request|, run these steps: +NOTE: The intent of this algorithm is to always require user activation before a storage-access permission will be set. Though it is within the means of user agents to set storage-access permissions based on custom heuristics without prior user activation, this specification strongly discourages such behavior, as it could lead to interoperability issues. -1. Let |settings| be |request|'s [=request/client=]'s [=relevant global object=]'s [=relevant settings object=]. -1. Let |embedded origin| be |request|'s [=request/url=]'s [=url/origin=]. -1. Let |descriptor| be a newly created {{TopLevelStorageAccessPermissionDescriptor}} with {{PermissionDescriptor/name}} set to "top-level-storage-access" and with {{TopLevelStorageAccessPermissionDescriptor/requestedOrigin}} set to |embedded origin|. -1. Let |existing state| be |descriptor|'s [=permission state=] with |settings|. -1. If |existing state| is [=permission/granted=], return true. -1. Return false. +ISSUE(privacycg/requestStorageAccessFor#15): The permissions task source shouldn't be used directly.

Permissions Integration

-The requestStorageAccessFor API defines a [=powerful feature=] identified by the [=powerful feature/name=] "top-level-storage-access". It defines the following permission-related algorithms: - -
-
{{PermissionDescriptor}}
-
- The "top-level-storage-access" [=powerful feature=] defines a {{PermissionDescriptor}} as follows: -
-        dictionary TopLevelStorageAccessPermissionDescriptor : PermissionDescriptor {
-            USVString requestedOrigin = "";
-        };
-    
-
-
[=powerful feature/permission query algorithm=]
-
-
- To query the "top-level-storage-access" permission, given a {{PermissionDescriptor}} |permissionDesc| and a {{PermissionStatus}} |status|, run the following steps: - - 1. Set |status|'s {{PermissionStatus/state}} to |permissionDesc|'s [=permission state=]. - 1. If |status|'s {{PermissionStatus/state}} is [=permission/denied=], set |status|'s {{PermissionStatus/state}} to [=permission/prompt=]. - - Note: The [=permission/denied=] permission state is not revealed to avoid exposing the user's decision to developers. This is done to prevent retaliation against the user and repeated prompting to the detriment of the user experience. - -
-
-
[=powerful feature/permission key type=]
-
- A [=permission key=] of the "top-level-storage-access" feature has the type [=site=]. - - Note: the {{TopLevelStorageAccessPermissionDescriptor/requestedOrigin}} field ensures that the [=permission store entry=] is double-keyed. -
-
[=powerful feature/permission key generation algorithm=]
-
-
- To generate a new [=permission key=] for the "top-level-storage-access" feature, given an [=environment settings object=] |settings|, run the following steps: - 1. Let |current origin| be |settings|' [=environment settings object/origin=]. - 1. If |current origin| is not [=same site=] with |settings|' [=top-level origin=], return null. - 1. Return the result of [=obtain a site|obtaining a site=] from |settings|' [=top-level origin=]. - - Note: the check for whether |settings|' [=environment settings object/origin=] is [=same site=] with |settings|' [=top-level origin=] is intended to disallow permission queries from cross-site frames. - This depends on the invariant that `top-level-storage-access` permission requests are only allowed in a [=top-level browsing context=]. As such, this check is only relevant in {{Permissions/query(permissionDesc)}}. - -
-
-
[=powerful feature/permission key comparison algorithm=]
-
-
- To compare [=permission keys=] |key1| and |key2| for the "top-level-storage-access" feature, run the following steps: - 1. If |key1| is null or |key2| is null, return false. - 1. Return |key1| is [=same site=] with |key2|. - -
-
-
+The requestStorageAccessFor API utilizes the existing [=powerful feature=] identified by the [=powerful feature/name=] "storage-access" and the integration with permissions is defined in the spec where the permission was defined. +

Fetch Integration

@@ -226,21 +188,6 @@ In [=http network or cache fetch=], when determining whether to block cookies, r -

Storage Access API Integration

- -Note: even after a successful {{Document/requestStorageAccessFor(requestedOrigin)}} call, frames have to explicitly invoke {{Document/requestStorageAccess()}} for cookie access. -This modification allows {{Document/requestStorageAccessFor(requestedOrigin)}} to allow resolution of {{Document/requestStorageAccess()}} calls similarly to a prior successful {{Document/requestStorageAccess()}} grant. - -
-Modify {{Document/requestStorageAccess()}} to insert the following steps before step 13.4 (i.e. before checking transient activation): - -1. Let |settings| be doc's [=relevant settings object=]. -1. Let |origin| be |settings|' [=environment settings object/origin=]. -1. Let |descriptor| be a newly created {{TopLevelStorageAccessPermissionDescriptor}} with {{PermissionDescriptor/name}} set to "top-level-storage-access" and with {{TopLevelStorageAccessPermissionDescriptor/requestedOrigin}} set to |origin|. -1. If |descriptor|'s [=permission state=] is [=permission/granted=], [=queue a global task=] on the [=permissions task source=] given |global| to [=resolve=] |p|, and return. -1. If |descriptor|'s [=permission state=] is [=permission/denied=], [=queue a global task=] on the [=permissions task source=] given |global| to [=reject=] |p| with a "{{NotAllowedError}}" {{DOMException}}, and return. - -

Privacy considerations

From cc10534ae86efeb426eb677013757f894d51dee4 Mon Sep 17 00:00:00 2001 From: Aaron Selya Date: Mon, 10 Mar 2025 11:26:47 -0400 Subject: [PATCH 3/4] Update auto-publish.yml --- .github/workflows/auto-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/auto-publish.yml b/.github/workflows/auto-publish.yml index 9a33415..4fca81d 100644 --- a/.github/workflows/auto-publish.yml +++ b/.github/workflows/auto-publish.yml @@ -6,7 +6,7 @@ on: jobs: main: name: Build, Validate and Deploy - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: w3c/spec-prod@v2 From 04bfc719423942249a7d00494f070c81bdd3fd0f Mon Sep 17 00:00:00 2001 From: Aaron Selya Date: Mon, 10 Mar 2025 14:09:07 -0400 Subject: [PATCH 4/4] Update to fix linker issues --- index.bs | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/index.bs b/index.bs index ea9cfdf..78bd1d6 100644 --- a/index.bs +++ b/index.bs @@ -30,9 +30,27 @@ urlPrefix: https://privacycg.github.io/storage-access/#; spec: storage-access type: dfn text: determine the storage access policy; url: determine-the-storage-access-policy text: determine if a site has storage access; url: determine-if-a-site-has-storage-access + text: has storage access; url: environment-has-storage-access + text: determine whether the user agent explicitly allows unpartitioned cookie access;url: determine-whether-the-user-agent-explicitly-allows-unpartitioned-cookie-access urlPrefix: https://fetch.spec.whatwg.org/#; spec: fetch type: dfn text: http network or cache fetch; url: http-network-or-cache-fetch + +urlPrefix: https://w3c-fedid.github.io/FedCM/; spec: fedcm + type: dfn + text: connected accounts set; url: browser-connected-accounts-set + text: IDP; url: idp + text: RP; url: rp + text: determining the effective FedCM connection status; url: determine-the-fedcm-site-connection-status + +urlPrefix: https://privacycg.github.io/storage-access-headers/#; spec: storage-access-headers + type: dfn + text: storage access headers retry check; url: perform-a-storage-access-retry-check + text: storage access status; url : storage-access-status + text: active url: storage-access-status-active + + +
 {
@@ -121,12 +139,12 @@ When invoked on {{Document}} |doc| with {{USVString}} |requestedOrigin|, the 
 In [=http network or cache fetch=], when determining whether to block cookies, run the following algorithm. A true result means cookies can be unblocked:
-1. Let |has top-level access| be the result of running [=determine if a request has top-level storage access=] on |request|.
-1. If |has top-level access| is false, return false.
+1. Let |has storage access| be the result of running [=storage access headers retry check=] on |request|.
+1. If |has storage access| is false, return false.
 1. Let |is subresource| be true if |request| is a [=subresource request=] and false otherwise.
-1. Let |allowed subresource mode| be true if |request|'s [=request/storage access status=] is "[=storage access status/active=]", and false otherwise.
+1. Let |allowed subresource mode| be true if |request|'s [=storage access status=] is "[=active=]", and false otherwise.
 1. If |is subresource| is true and |allowed subresource mode| is false, return false.
 1. If |request|'s [=request/client=]'s [=relevant global object=]'s [=associated document=] is not a [=traversable navigable=], return false.
 1. Return true.
@@ -203,11 +221,6 @@ The prompts used have to be careful to indicate the direction of the request, su
 As with {{Document/requestStorageAccess()}}, the same tension between user consent and prompt fatigue exists with {{Document/requestStorageAccessFor(requestedOrigin)}}; much like the Storage Access API,
 [=implementation-defined=] acceptance and rejection steps are intended to enable implementers with differing stances on this question to make compromises as they see fit.
 
-Another difference is that queries for the permission can be more sensitive, depending on the context. Note that a frame has to be unable to request the state of either of:
-* Whether it was [=permission/granted=] a "top-level-storage-access" permission for some origin while a top-level document.
-* Whether arbitrary other origins were [=permission/granted=] the "top-level-storage-access" on the current top-level site.
-
-In the former case, this would allow bogus domains (or combinations thereof) to be used as identifiers; in the latter case, it would reveal state under unrelated origins.
 
 

Security considerations

@@ -224,7 +237,7 @@ For frame access, {{Document/requestStorageAccessFor(requestedOrigin)}} merely s The specific security controls proposed by the API are: * Any cookies included with the subresource request have to be explicitly marked `SameSite=None`, indicating intent for use in [=third party contexts=]. -* For any `SameSite=None` cookies to be included, the request's [=request/storage access status=] must be "[=storage access status/active=]". +* For any `SameSite=None` cookies to be included, the request's [=storage access status=] must be "[=active=]". Additionally, only requests initiated from the top-level document will be eligible for inclusion of `SameSite=None` cookies. This ensures that other embedded frames do not receive escalated privileges.