generated from privacycg/template
-
Notifications
You must be signed in to change notification settings - Fork 28
/
storage-access.bs
513 lines (397 loc) · 35.9 KB
/
storage-access.bs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
<pre class="metadata">
Title: The Storage Access API
Shortname: storage-access
Repository: privacycg/storage-access
URL: https://privacycg.github.io/storage-access/
Editor: Benjamin VanderSloot, w3cid 135256, Mozilla https://mozilla.org, [email protected]
Editor: Johann Hofmann, w3cid 120436, Google https://google.com, [email protected]
Editor: Anne van Kesteren, w3cid 38001, Apple Inc. https://apple.com, [email protected]
Former Editor: John Wilander, w3cid 89478, Apple Inc. https://apple.com, [email protected]
Former Editor: Theresa O’Connor, w3cid 40614, Apple Inc. https://apple.com, [email protected]
Abstract: The Storage Access API enables content in iframes to request access to website data (such as cookies).
Status Text: This specification is intended to be merged into the HTML Living Standard. It is neither a WHATWG Living Standard nor is it on the standards track at W3C.
Text Macro: LICENSE <a href=https://creativecommons.org/licenses/by/4.0/>Creative Commons Attribution 4.0 International License</a>
Group: privacycg
Status: CG-DRAFT
Level: None
Markup Shorthands: markdown yes, css no
Complain About: accidental-2119 true
</pre>
<!-- The main rSA and hSA implementations live in these files: -->
<!-- https://trac.webkit.org/browser/webkit/trunk/Source/WebCore/dom/DocumentStorageAccess.cpp -->
<!-- https://searchfox.org/mozilla-central/source/dom/base/Document.cpp -->
<!-- https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/storage_access/document_storage_access.cc -->
<!-- File issues on HTML to export each of these -->
<pre class=link-defaults>
spec:infra; type:dfn; text:user agent
</pre>
<pre class="anchors">
urlPrefix: https://fetch.spec.whatwg.org/; spec: Fetch
text: http-network-or-cache fetch; url: #concept-http-network-or-cache-fetch; type: dfn
spec: RFC6265; urlPrefix: https://tools.ietf.org/html/rfc6265
type: dfn
text: cookie store; url: section-5.3
spec: RFC6265bis; urlPrefix: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-11
type: dfn
text: site for cookies; url: section-5.2.1
urlPrefix: https://w3c.github.io/webdriver/webdriver-spec.html#; spec: webdriver
type: dfn
text: current browsing context; url: dfn-current-browsing-context
text: WebDriver error; url: dfn-error
text: WebDriver error code; url: dfn-error-code
text: extension command; url: dfn-extension-commands
text: extension command URI template; url: dfn-extension-command-uri-template
text: getting a property; url: dfn-getting-properties
text: invalid argument; url: dfn-invalid-argument
text: local end; url: dfn-local-end
text: remote end steps; url: dfn-remote-end-steps
text: unknown error; url: dfn-unknown-error
text: unsupported operation; url: dfn-unsupported-operation
text: session; url: dfn-session
text: success; url: dfn-success
spec: html; urlPrefix: https://html.spec.whatwg.org/multipage/
type: dfn
text: source snapshot params; url: browsing-the-web.html#source-snapshot-params
text: snapshotting source snapshot params; url: browsing-the-web.html#snapshotting-source-snapshot-params
text: create navigation params by fetching; url: browsing-the-web.html#create-navigation-params-by-fetching
text: set up a window environment settings object; url: nav-history-apis.html#set-up-a-window-environment-settings-object
text: environment
spec: fetch; urlPrefix: https://fetch.spec.whatwg.org/
type: dfn
for: response
text: has-cross-origin-redirects; url: #response-has-cross-origin-redirects
spec: fedcm; urlPrefix: https://w3c-fedid.github.io/FedCM/
type: dfn
text: connected accounts set; url: browser-connected-accounts-set
text: IDP; url: idp
text: RP; url: rp
spec: credential-management-1; urlPrefix: https://w3c.github.io/webappsec-credential-management/
type: dfn
text: prevent silent access flag; url: origin-prevent-silent-access-flag
spec: permissions-policy; urlPrefix: https://w3c.github.io/webappsec-permissions-policy/
type: dfn
text: is feature enabled; url: algo-is-feature-enabled
</pre>
<pre class=biblio>
{
"STORAGE-ACCESS-INTRO": {
"authors": ["John Wilander"],
"date": "February 2018",
"href": "https://webkit.org/blog/8124/introducing-storage-access-api/",
"publisher": "WebKit",
"rawDate": "2018-02-21",
"status": "Blog post",
"title": "Introducing Storage Access API"
}
}
</pre>
<style>
.XXX {
color: #E50000;
font-weight: bold;
}
.XXX::before {
content: "TODO: ";
}
</style>
<section class="non-normative">
<h2 id="intro">Introduction</h2>
<em>This section is non-normative.</em>
User Agents sometimes prevent content inside certain <{iframe}>s from accessing data stored in client-side storage mechanisms like cookies. This can break embedded content which relies on having access to client-side storage.
The Storage Access API enables content inside <{iframe}>s to request and be granted access to their client-side storage, so that embedded content which relies on having access to client-side storage can work in such User Agents. [[STORAGE-ACCESS-INTRO]]
</section>
<h2 id="infra">Infrastructure</h2>
This specification depends on the Infra standard. [[!INFRA]]
<h2 id="the-storage-access-api">The Storage Access API</h2>
This specification defines a method to query whether or not a {{Document}} currently has access to its [=unpartitioned data=] ({{Document/hasStorageAccess()}}), and a method that can be used to request access to its [=unpartitioned data=] ({{Document/requestStorageAccess()}}).
<div class=example>
Alex visits `https://social.example/`. The page sets a cookie. This cookie has been set in a [=first-party-site context=].
Later on, Alex visits `https://video.example/`, which has an <{iframe}> on it which loads `https://social.example/heart-button`. In this case, the `social.example` {{Document}} |doc| is in a [=third party context=], and the cookie set previously might or might not be visible from |doc|`.`{{Document/cookie}}, depending on User Agent storage access policies.
Script in the <{iframe}> can call |doc|`.`{{Document/hasStorageAccess()}} to determine if it has access to the cookie. If it does not have access, it can request access by calling |doc|`.`{{Document/requestStorageAccess()}}.
</div>
<dfn>Unpartitioned data</dfn> is client-side storage that would be available to a [=site=] were it loaded in a [=first-party-site context=].
A {{Document}} is in a <dfn>first-party-site context</dfn> if it is the [=active document=] of a [=top-level browsing context=]. Otherwise, it is in a [=first-party-site context=] if it is an [=active document=] and the [=environment settings object/origin=] and [=top-level origin=] of its [=relevant settings object=] are [=/same site=] with one another.
A {{Document}} is in a <dfn>third party context</dfn> if it is not in a [=first-party-site context=].
To <dfn>determine whether the user agent explicitly allows unpartitioned cookie access</dfn>, given a [=tuple=] |tuple| consisting of two [=sites=], run the following steps. This algorithm returns "`none`", "`allow`" or "`disallow`".
Note: A user agent's settings might explicitly allow or disallow unpartitioned cookie access through per-site allow-lists, the user changing global browser settings, or similar custom overrides.
1. If the user agent does not have explicit settings for unpartitioned cookie access for |tuple|, return "`none`".
1. If the user agent's settings explicitly allow unpartitioned cookie access for |tuple|, return "`allow`".
1. [=Assert=]: the user agent's settings explicitly disallow unpartitioned cookie access for |tuple|.
1. Return "`disallow`".
To <dfn>determine the FedCM site connection status</dfn> given a [=/origin=] |embedder| and [=/origin=] |identityProvider|, run the following steps. This algorithm returns a [=boolean=].
1. [=list/iterate|For each=] |item| of [=connected accounts set=]:
1. Let (|rp|, |idp|, <var ignore=''>account</var>) be |item|.
1. If |rp| and |embedder| are [=/same site=], and |idp| and |identityProvider| are [=/same site=], return true.
1. Return false.
To <dfn>determine the effective FedCM connection status</dfn> given a [=/origin=] |embedder|, a [=/origin=] |identityProvider|, a {{Document}} |doc|, run the following steps. This algorithm returns a [=boolean=].
1. Let |policyStatus| be whether |doc| is [=allowed to use=] "`identity-credentials-get`".
1. If |policyStatus| is "Disabled", return false.
1. Let |connected| be the result of [=determine the FedCM site connection status|determining the site connection status=] given |embedder| and |identityProvider|.
1. If |connected| is false, return false.
1. Let |preventSilentAccess| be [=user agent=]'s [=credential store=]'s [=prevent silent access flag=] for |embedder|.
1. If |preventSilentAccess|, return false.
1. Return true.
<h3 id="ua-state">Changes to user agent state related to storage access</h3>
Modify the definition of [=environment=] in the following manner:
1. Add a new member called <dfn for="environment">has storage access</dfn> of type [=boolean=].
Modify the definition of [=source snapshot params=] in the following manner:
1. Add a new member called <dfn for="source snapshot params">has storage access</dfn> of type [=boolean=].
1. Add a new member called <dfn for="source snapshot params">environment id</dfn> of type opaque [=string=].
<h3 id="the-document-object">Changes to {{Document}}</h3>
<pre class="idl">
partial interface Document {
Promise<boolean> hasStorageAccess();
Promise<undefined> requestStorageAccess();
};
</pre>
When invoked on {{Document}} |doc|, the <dfn export method for=Document><code>hasStorageAccess()</code></dfn> method must run these steps:
<!-- https://developer.mozilla.org/en-US/docs/Web/API/Document/hasStorageAccess -->
1. Let |p| be [=a new promise=].
1. If |doc| is not [=Document/fully active=], then [=/reject=] |p| with an "{{InvalidStateError}}" {{DOMException}} and return |p|.
1. If |doc|'s [=Document/origin=] is an [=opaque origin=], [=/resolve=] |p| with false and return |p|.
1. Let |global| be |doc|'s [=relevant global object=].
1. If |global| is not a [=secure context=], then [=/resolve=] |p| with false and return |p|.
1. If the [=top-level origin=] of |doc|'s [=relevant settings object=] is an [=opaque origin=], [=/resolve=] |p| with false and return |p|.
1. Let |browsingContext| be |doc|'s [=Document/browsing context=].
1. Let |topLevelSite| be the result of [=obtain a site|obtaining a site=] from the [=top-level origin=] of |doc|'s [=relevant settings object=].
1. Let |embeddedSite| be the result of [=obtain a site|obtaining a site=] from |doc|'s [=Document/origin=].
1. Run the following steps [=in parallel=]:
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. Let |permissionState| be the result of [=getting the current permission state=] given "<a permission><code>storage-access</code></a>" and |global|.
1. [=Queue a global task=] on the [=networking task source=] given |global| to:
1. If |explicitSetting| is "`disallow`", [=/resolve=] |p| with false.
1. If |explicitSetting| is "`allow`", [=/resolve=] |p| with true.
1. [=Assert=]: |explicitSetting| is "`none`".
1. If |browsingContext| is a [=top-level browsing context=], [=/resolve=] |p| with true.
1. If |browsingContext| is same authority with |browsingContext|'s [=top-level browsing context=]'s [=active document=], [=/resolve=] |p| with true.
ISSUE: "same authority" here is a placeholder for a future concept that allows user agents to perform [=/same site=] checks while adhering to additional security aspects such as the presence of a cross-site parent document, see [whatwg/storage#142](https://github.com/whatwg/storage/issues/142#issuecomment-1122147159). In practice, this might involve comparing the [=site for cookies=] or performing a [=/same site=] check with the top-level document.
1. If |permissionState| is [=permission/granted=], [=/resolve=] |p| with |global|'s [=environment/has storage access=].
Note: The global storage access permission state takes precedence over the local [=environment/has storage access=] flag here, in order to immediately reflect a possible user choice to revoke the permission in their settings.
1. [=/Resolve=] |p| with false.
1. Return |p|.
When invoked on {{Document}} |doc|, the <dfn export method for=Document><code>requestStorageAccess()</code></dfn> method must run these steps:
<!-- https://developer.mozilla.org/en-US/docs/Web/API/Document/requestStorageAccess -->
1. Let |p| be [=a new promise=].
1. If |doc| is not [=Document/fully active=], then [=/reject=] |p| with an "{{InvalidStateError}}" {{DOMException}} and return |p|.
1. Let |global| be |doc|'s [=relevant global object=].
1. Let |settings| be |doc|'s [=relevant settings object=].
1. If |global| is not a [=secure context=], then [=/reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and return |p|.
1. If |doc| is not [=allowed to use=] "`storage-access`", [=/reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and return |p|.
1. If |doc|'s [=Document/origin=] is an [=opaque origin=], [=/reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and return |p|.
1. If |settings|'s [=top-level origin=] is an [=opaque origin=], [=/reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and return |p|.
1. If |doc|'s [=active sandboxing flag set=] has its [=sandbox storage access by user activation flag=] set, [=/reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and return |p|.
1. Let |browsingContext| be |doc|'s [=Document/browsing context=].
1. Let |topLevelOrigin| be the [=top-level origin=] of |doc|'s [=relevant settings object=].
1. Let |topLevelSite| be the result of [=obtain a site|obtaining a site=] from |topLevelOrigin|.
1. Let |embeddedOrigin| be |doc|'s [=Document/origin=].
1. Let |embeddedSite| be the result of [=obtain a site|obtaining a site=] from |embeddedOrigin|.
1. Let |has transient activation| be whether |doc|'s {{Window}} object has [=transient activation=].
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 "<a permission><code>storage-access</code></a>" 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. If |has transient activation| is false:
1. Run |process permission state| with [=permission/denied=].
1. Abort these steps.
1. Let |permissionState| be the result of [=requesting permission to use=] "<a permission><code>storage-access</code></a>".
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|.
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.
<h3 id="navigation">Changes to navigation</h3>
When [=snapshotting source snapshot params=]:
1. Set [=source snapshot params/has storage access=] to |sourceDocument|'s [=source snapshot params/has storage access=].
1. Set [=source snapshot params/environment id=] to |sourceDocument|'s [=relevant settings object=]'s [=environment/id=].
To the [=create navigation params by fetching=] algorithm, insert the following step as step 3:
1. Let |originalURL| be <var ignore>entry</var>'s URL.
When creating |request|'s [=reserved client=] in [=create navigation params by fetching=]:
1. Set [=reserved client=]'s [=environment/has storage access=] to |sourceSnapshotParams|'s [=source snapshot params/has storage access=] if all of the following hold:
1. |sourceSnapshotParams|'s [=source snapshot params/environment id=] equals <var ignore>navigable</var>'s [=active document=]'s [=relevant settings object=]'s [=environment/id=].
1. |originalURL|'s [=url/origin=] is [=same origin=] with <var ignore>currentURL</var>'s [=url/origin=].
1. |response| is null or |response|'s [=response/has-cross-origin-redirects=] is false.
1. Otherwise, set |request|'s [=reserved client=]'s [=environment/has storage access=] to false.
When [=set up a window environment settings object|setting up a window environment settings object=]:
1. Set <var ignore>settings object</var>'s [=environment/has storage access=] to <var ignore>reserved environment</var>'s [=environment/has storage access=].
<h3 id="storage">Changes to various client-side storage mechanisms</h3>
This API only impacts HTTP cookies. A future revision of this API might impact other client-side state. [[!RFC6265]]
<h4 id="cookies">Cookies</h4>
This API is intended to be used with environments and user agent configurations that block access to unpartitioned cookies in a [=third party context=]. At the time of this writing, this concept has not yet been integrated into the [=HTTP-network-or-cache fetch=] and {{Document/cookie}} algorithms. To allow for such an integration, the [=cookie store=] will need to be modified to receive information about the top-level and embedded site of the request (to determine whether to attach cross-site, partitioned, or no cookies) as well as whether the request was made for a document that has storage access, through accessing the [=environment=]'s [=environment/has storage access=] that is defined in this specification.
Once the cookie store allows for receiving information about storage access, we would update [=HTTP-network-or-cache fetch=] and {{Document/cookie}} to pass the [=environment=]'s [=environment/has storage access=] to the [=cookie store=] when retrieving cookies.
When getting unpartitioned cookies from the [=cookie store=] with storage access, user agents will still follow applicable `SameSite` restrictions (i.e., not attach cookies marked `SameSite=Strict` or `SameSite=Lax` in [=third party contexts=]).
Note: User agents could apply different default values for the `SameSite` cookie attribute. This could lead to unpartitioned cookies without a `SameSite` attribute being attached to requests in some user agents (where `SameSite=None` is the default), but not in others (where `SameSite=Lax` is the default). Web developers are encouraged to set the `SameSite` attribute on their cookies to not run into issues.
<h3 id="sandboxing-storage-access">Sandboxing storage access</h3>
A [=/sandboxing flag set=] has a <dfn export>sandbox storage access by user activation flag</dfn>. This flag prevents content from requesting storage access.
To the [=parse a sandboxing directive=] algorithm, add the following under step 3:
<ul>
<li>The [=sandbox storage access by user activation flag=], unless <var ignore>tokens</var> contains the <dfn export attr-value for=iframe/sandbox>allow-storage-access-by-user-activation</dfn> keyword.
</ul>
<h2 id="permissions-integration">Permissions Integration</h2>
The Storage Access API defines a [=powerful feature=] identified by the [=powerful feature/name=] "<dfn export permission><code>storage-access</code></dfn>". It defines the following permission-related algorithms:
<dl>
<dt>[=powerful feature/permission query algorithm=]</dt>
<dd>
To query the "<a permission><code>storage-access</code></a>" permission, given a {{PermissionDescriptor}} |permissionDesc| and a {{PermissionStatus}} |status|:
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 "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.
</dd>
<dt>[=powerful feature/permission key type=]</dt>
<dd>
A [=permission key=] of the "<a permission><code>storage-access</code></a>" feature is a [=tuple=] consisting of a [=site=] <dfn for="permission key">top-level</dfn> and a [=site=] <dfn for="permission key">requester</dfn>.
<div class=example>
`(("https", "news.example"), ("https", "social.example"))` is a [=permission key=] for "<a permission><code>storage-access</code></a>" whose [=permission key/top-level=] is `("https", "news.example")` and whose [=permission key/requester=] is `("https", "social.example")`.
</div>
</dd>
<dt>[=powerful feature/permission key generation algorithm=]</dt>
<dd>
To generate a new [=permission key=] for the "<a permission><code>storage-access</code></a>" feature, given an [=environment settings object=] |settings|, run the following steps:
1. Let |topLevelSite| be the result of [=obtain a site|obtaining a site=] from |settings|' [=environment/top-level origin=].
1. Let |embeddedSite| be the result of [=obtain a site|obtaining a site=] from |settings|' [=environment settings object/origin=].
1. Return (|topLevelSite|, |embeddedSite|).
</dd>
<dt>[=powerful feature/permission key comparison algorithm=]</dt>
<dd>
To compare the [=permission keys=] |key1| and |key2| for the "<a permission><code>storage-access</code></a>" feature, run the following steps:
1. If |key1|'s [=permission key/top-level=] is not [=site/same site=] with |key2|'s [=permission key/top-level=], return false.
1. If |key1|'s [=permission key/requester=] is not [=site/same site=] with |key2|'s [=permission key/requester=], return false.
1. Return true.
</dd>
</dl>
<h2 id="permissions-policy-integration">Permissions Policy Integration</h2>
The Storage Access API defines a [=policy-controlled feature=] identified by the string `"storage-access"`. Its [=default allowlist=] is `"*"`.
Note: A {{Document}}’s [=Document/permissions policy=] determines whether any content in that document is allowed to request storage access using {{Document/requestStorageAccess()}}. If disabled in any document, calling {{Document/requestStorageAccess()}} in that document will reject.
<h2 id="privacy">Privacy considerations</h2>
The Storage Access API enables the removal of cross-site cookies. Specifically, it allows the authenticated embeds use case to continue to work. As such, the API provides a way for developers to re-gain access to cross-site cookies, albeit under further constraints.
A nested {{Document}} gains access to the same cookies it has as the [=active document=] of a [=top-level browsing context=] when it calls {{Document/requestStorageAccess()}} and is returned a resolving {{Promise}}. With these cookies it can authenticate itself to the server and load user-specific information.
While this functionality comes with a risk of abuse by third parties for tracking purposes, it is an explicit goal of the API and a key to its design to not undermine the gains of cross-site cookie deprecation.
Importantly, we do not degrade privacy properties when compared to pre-removal of cross-site cookies. This follows from a lack of platform-specific information used in the spec to prevent stateless tracking and the only state added being a permission scoped to the [=site|sites=] of the embedding and embedded [=Document=].
Our privacy considerations are more challenging where default cross-site cookies are already deprecated. The challenge is to decide when and how to permit the Storage Access API to be used to revert a cookie-less (or cookie-partitioned) nested {{Document}} to a pre-deprecation state, giving it access to its [=unpartitioned data=].
In an ideal case, a nested {{Document}} would only be able to gain access to its [=unpartitioned data=] if:
1. the user interacts with the nested {{Document}}
2. the nested {{Document}} is permitted by the embedder to use the API
3. the nested {{Document}} is a [=secure context=]
4. the user grants express, pairwise permission to the embeddee to use its cookies in the embedder
5. the user is not inundated by requests for the "<code>storage-access</code>" permission so their express permission is not undermined by fatigue
This specification requires the first three of implementers. This provides guarantees that the user is aware of the content of the nested {{Document}}, the embedder has not opted out of the nested {{Document}}'s authentication, and the cross-site cookies are not disclosed to network attackers, respectively.
The last two points are in tension. In an ideal world, we would show a prompt to the user in every call to {{Document/requestStorageAccess()}}. But, this would allow pages to prompt the user so frequently as to put the last point at the discretion of the page– a state we find unacceptable. User agents should prevent over-prompting of the user.
<figure id=example-prompt>
<img src=images/storage-access-prompt.png
alt="A modal dialog box which states 'Do you want to allow “video.example” to use cookies and website data while browsing “news.example”? This will allow “video.example” to track your activity.' and which has two buttons, “Don’t Allow” and “Allow”.">
<figcaption>An example prompt which could be shown to the user when a site calls `document.`{{Document/requestStorageAccess()}}.</figcaption>
</figure>
Thus, the last two points represent a key point of compromise. We permit [=implementation-defined=] behavior on when to grant or deny requests for [=unpartitioned data=] without requiring user choice so long as they meet all other requirements. This compromise weakens the privacy guarantees of this proposal, specifically point 4, to a degree under the control of the implementer and gives the implementer the power to render it impossible to get storage access with this API. However, this has proven necessary to enable condition 5 to be possible given our implementers' differing stances on the compromise between these two points.
Developer experience suffers where user agents differ greatly in their [=implementation-defined=] behavior, and therefore user agents should aim to minimize or standardize silent grants and denies.
<h3 id="site-scope">Permission scope</h3>
Another tension in the design of the API is what to use to key the "<code>storage-access</code>" permission: [=/origin|origins=] or [=site|sites=]. We chose [=site|sites=] because we believe them to be acceptable boundaries for privacy while enabling existing uses of [=site/same site=] and cross-origin nested {{Document|Documents}} on the same page with only one user prompt.
<h2 id="security">Security considerations</h2>
It is important that this spec not degrade security properties of the web platform, even when compared to post-removal of cross-site cookies. Third-party cookie removal has potential benefits for security, specifically in mitigating attacks that rely upon authenticated requests, e.g. CSRF. We do not wish the Storage Access API to be a foothold for such attacks to leverage.
To this end, we limit the impact of a "<code>storage-access</code>" permission grant to only give access to [=unpartitioned data=] to the nested {{Document}} that called {{Document/requestStorageAccess()}} and only until the nested {{Document}} navigates across an [=/origin=] boundary. This ensures that only [=/origin|origins=] with a page that call {{Document/requestStorageAccess()}} will be making credentialed requests, and moreover the embedee page can control which embedder it permits via the Content Security Policy "<code>frame-ancestors</code>" directive. This retains an [=/origin=]-scoped control for security purposes by the embedee.
<h3 id="reputation">Reputational attacks</h3>
This also is effective at preventing another attack: one on the embedee's reputation. We consider any cross-site authenticated request to have potential reputational harm as consumers become more privacy conscious. Therefore a first-party or sibling cross-site causing an embedded resource to be requested with the user's authentication cookies would constitute an attack on the reputation of that cross-site's owner. This is also a reason we require this API to be used in a [=secure context=]: so a network adversary cannot induce an embedee to use this API.
The embedder has control over which nested {{Document|Documents}} have the ability to become authenticated, or even display a permission request to the user via the Permission Policy and nested {{Document}} sandboxing.
<h3 id="notification">Notification abuse</h3>
Notification abuse was also considered while specifying the Storage Access API. Specifically, we require user interaction in the nested {{Document}} and consume that rejection on a denial to restrict the conditions a permission prompt will be shown to the user. This mitigates attacks such as re-requesting a permission immediately after the user denies it.
<h2 id="automation">Automation</h2>
For the purposes of user-agent automation and application testing, this document defines the following [=extension command=] for the [[WebDriver]] specification.
<h3 id="set-storage-access-command">Set Storage Access</h3>
<table>
<tbody>
<tr>
<th>HTTP Method</th>
<th>URI Template</th>
</tr>
<tr>
<td>POST</td>
<td>/session/{session id}/storageaccess</td>
</tr>
</tbody>
</table>
The <dfn export>Set Storage Access</dfn> [=extension command=] modifies the storage access policy for the [=current browsing context=].
The [=remote end steps=] are:
1. Let |blocked| be the result of [=getting a property=] from |parameters| named `blocked`.
1. If |blocked| is not a [=boolean=] return a [=WebDriver error=] with [=WebDriver error code=] [=invalid argument=].
1. Let |embedded origin| be the result of [=getting a property=] from |parameters| named `origin`.
1. If |embedded origin| is not a single U+002A ASTERISK character (*), then:
1. Let |parsedURL| be the the result of running the [=URL parser=] on |embedded origin|.
1. If |parsedURL| is failure, then return a [=WebDriver error=] with [=WebDriver error code=] [=invalid argument=].
1. Set |embedded origin| to |parsedURL|'s [=url/origin=].
1. If the [=current browsing context=] is not a [=top-level browsing context=] return a [=WebDriver error=] with [=WebDriver error code=] [=unsupported operation=].
1. Let |doc| be the [=current browsing context=]'s [=active document=].
1. Let |settings| be |doc|'s [=relevant settings object=].
1. Let |top-level site| be the result of [=obtain a site|obtaining a site=] from |settings|'s [=environment settings object/origin=].
1. If |embedded origin| is a single U+002A ASTERISK character (*), then:
1. If |blocked| is `true`, then:
1. Run an [=implementation-defined=] set of steps to ensure that no site has access to its [=unpartitioned data=] when loaded in a [=third party context=] on |top-level site|.
1. Otherwise, if |blocked| is `false`, then:
1. Run an [=implementation-defined=] set of steps to ensure that any site has access to its [=unpartitioned data=] when loaded in a [=third party context=] on |top-level site|.
1. Otherwise:
1. If |embedded origin| is [=/same site=] with |settings|'s [=environment settings object/origin=] return a [=WebDriver error=] with [=WebDriver error code=] [=unsupported operation=].
1. If |blocked| is `true`, then:
1. Run an [=implementation-defined=] set of steps to ensure that |embedded origin| does not have access to its [=unpartitioned data=] when loaded in a [=third party context=] on |top-level site|.
1. Otherwise, if |blocked| is `false`, then:
1. Run an [=implementation-defined=] set of steps to ensure that |embedded origin| has access to its [=unpartitioned data=] when loaded in a [=third party context=] on |top-level site|.
1. If the above [=implementation-defined=] step of steps resulted in failure, return a [=WebDriver error=] with [=WebDriver error code=] [=unknown error=].
1. Return [=success=] with data `null`.
<h2 id="acknowledgements" class="no-num">Acknowledgements</h2>
This specification builds on the foundations created by former editors John Wilander, who invented the Storage Access API, and Theresa O’Connor, who wrote significant portions of the initial text. We are grateful for their ideas and contributions.
Many thanks to
Anne van Kesteren,
Ben Kelly,
Brad Girardeau,
Brad Hill,
Brady Eidson,
Brandon Maslen,
Chris Mills,
Dave Longley,
Domenic Denicola,
Ehsan Akhgari,
Geoffrey Garen,
Jack Frankland,
James Coleman,
James Hartig,
Jeffrey Yasskin,
Kushal Dave,
Luís Rudge,
Maciej Stachowiak,
Matias Woloski,
Mike O'Neill,
Mike West,
Pete Snyder,
Rob Stone,
Stefan Leyhane,
Steven Englehardt,
Travis Leithead,
Yan Zhu,
Zach Edwards,
and everyone who commented on [whatwg/html#3338](https://github.com/whatwg/html/issues/3338), [privacycg/proposals#2](https://github.com/privacycg/proposals/issues/2), and [privacycg/storage-access/issues](https://github.com/privacycg/storage-access/issues)
for their feedback on this proposal.
Thanks to the [WebKit Open Source Project](https://webkit.org/) for allowing us to use the [Storage Access API Prompt](#example-prompt) image, which was [originally published on webkit.org](https://webkit.org/blog/8311/intelligent-tracking-prevention-2-0/).