forked from privacycg/storage-access
-
Notifications
You must be signed in to change notification settings - Fork 0
/
storage-access.bs
472 lines (355 loc) · 33 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
<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>
<!-- File issues on HTML to export each of these -->
<pre class=link-defaults>
spec:html; type:dfn; for:site; text:same site
spec:webidl; type:dfn; text:resolve
</pre>
<pre class="anchors">
urlPrefix: https://tc39.github.io/ecma262/; spec: ECMASCRIPT
text: agent cluster; url: #sec-agent-clusters; type: dfn
urlPrefix: https://infra.spec.whatwg.org/; spec: INFRA
text: implementation-defined; url: #implementation-defined; type: dfn
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
urlPrefix: https://w3c.github.io/permissions/; spec: permissions
text: permissions task source; url: #permissions-task-source; type: dfn
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
</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=].
<h3 id="ua-state">User Agent state related to storage access</h3>
A <dfn>storage access map</dfn> is a [=map=] whose keys are [=partitioned storage keys=] and whose values are [=storage access flag sets=].
User Agents maintain a single <dfn>global storage access map</dfn>.
ISSUE(privacycg/storage-access#2): What is the lifecycle of the [=global storage access map=]? How long do we remember its contents? Firefox and Safari differ here.
ISSUE(privacycg/storage-access#5): When do we age out entries in the [=global storage access map=]? See also [Scope of Storage Access](https://github.com/privacycg/storage-access#scope-of-storage-access).
Each [=agent cluster=] has a <dfn for="agent cluster">storage access map</dfn>.
When an [=agent cluster=] is created, its [=agent cluster/storage access map=] is initialized with a [=map/clone=] of the [=global storage access map=].
To <dfn type="abstract-op">obtain the storage access map</dfn> for a {{Document}} |doc|, run the following steps:
1. Return the [=agent cluster/storage access map=] of |doc|'s [=relevant agent=]'s [=agent cluster=].
A <dfn>partitioned storage key</dfn> is a [=tuple=] consisting of a <dfn for="partitioned storage key">top-level site</dfn> (a [=site=]) and an <dfn for="partitioned storage key">embedded origin</dfn> (an [=/origin=]).
<div class=example>
`(("https", "news.example"), ("https", "social.example", null, null))` is a [=partitioned storage key=] whose [=top-level site=] is `("https", "news.example")` and whose [=embedded origin=] is `("https", "social.example", null, null)`.
</div>
To <dfn type="abstract-op">generate a partitioned storage key</dfn> for a {{Document}} |doc|, run the following steps:
1. Let |settings| be |doc|'s [=relevant settings object=].
1. Let |site| be the result of [=obtain a site|obtaining a site=] from |settings|' [=environment settings object/origin=].
1. If |doc|'s [=Document/browsing context=] is a [=top-level browsing context=], return the [=partitioned storage key=] (|site|, |site|).
1. Let |top-level site| be the result of [=obtain a site|obtaining a site=] from |settings|' [=top-level origin=].
1. Return the [=partitioned storage key=] (|top-level site|, |site|).
A <dfn>storage access flag set</dfn> is a set of zero or more of the following flags, which are used to gate access to client-side storage for |embedded origin| when loaded in a [=third party context=] on |top-level site|:
: The <dfn for="storage access flag set" id=has-storage-access-flag>has storage access flag</dfn>
:: When set, this flag indicates |embedded origin| has access to its [=unpartitioned data=] when it's loaded in a [=third party context=] on |top-level site|.
To <dfn type="abstract-op">obtain a storage access flag set</dfn> for a [=partitioned storage key=] |key| from a [=/storage access map=] |map|, run the following steps:
1. If |map|[|key|] [=map/exists|does not exist=], run these steps:
1. Let |flags| be a new [=storage access flag set=].
1. [=map/Set=] |map|[|key|] to |flags|.
1. Return |map|[|key|].
<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 -->
<!-- https://trac.webkit.org/browser/webkit/trunk/Source/WebCore/dom/DocumentStorageAccess.cpp#L80 -->
<!-- https://hg.mozilla.org/mozilla-central/file/tip/dom/base/Document.cpp#l15512 -->
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 |doc|'s [=Document/browsing context=] is a [=top-level browsing context=], [=/resolve=] |p| with true 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|. <!-- https://github.com/privacycg/storage-access/issues/40 -->
1. If |doc|'s [=Document/origin=] is [=same origin=] with the [=top-level origin=] of |doc|'s [=relevant settings object=], [=/resolve=] |p| with true and return |p|.
1. Let |key| be the result of [=generate a partitioned storage key|generating a partitioned storage key=] from |doc|.
1. If |key| is failure, [=resolve=] |p| with false and return |p|.
1. Let |hasAccess| be the result of running [=determine if a site has storage access=] with |key| and |doc|.
1. [=Queue a global task=] on the [=permissions task source=] given |global| to [=/resolve=] |p| with |hasAccess|.
1. Return |p|.
ISSUE: Shouldn't step 8 be [=same site=]?
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 -->
<!-- https://trac.webkit.org/browser/webkit/trunk/Source/WebCore/dom/DocumentStorageAccess.cpp#L123 -->
<!-- https://hg.mozilla.org/mozilla-central/file/tip/dom/base/Document.cpp#l15629 -->
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. If |global| is not a [=secure context=], then [=reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and return |p|.
1. If this algorithm was invoked when |doc|'s {{Window}} object did not have [=transient activation=], [=reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and return |p|.
1. If |doc|'s [=Document/browsing context=] is a [=top-level browsing context=], [=/resolve=] and return |p|.
1. If |doc| is not [=allowed to use=] the `"storage-access"` permission, [=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 the [=top-level origin=] of |doc|'s [=relevant settings object=] is an [=opaque origin=], [=reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and return |p|. <!-- https://github.com/privacycg/storage-access/issues/40 -->
1. If |doc|'s [=Document/origin=] is [=same origin=] with the [=top-level origin=] of |doc|'s [=relevant settings object=], [=/resolve=] 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 |key| be the result of [=generate a partitioned storage key|generating a partitioned storage key=] from |doc|.
1. If |key| is failure, [=reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and return |p|.
1. Let |map| be the result of [=obtain the storage access map|obtaining the storage access map=] for |doc|.
1. Let |flag set| be the result of [=obtain a storage access flag set|obtaining the storage access flag set=] with |key| from |map|.
1. If |flag set|'s [=has storage access flag=] is set, [=/resolve=] and return |p|.
1. Otherwise, run these steps [=in parallel=]:
1. Let |hasAccess| be [=a new promise=].
1. [=Determine the storage access policy=] with |key|, |doc| and |hasAccess|.
1. [=Queue a global task=] on the [=permissions task source=] given |global| to
1. Set |flag set|'s [=has storage access flag=].
1. If |hasAccess| is true, resolve |p|.
1. Reject |p| with a "{{NotAllowedError}}" {{DOMException}}.
1. Return |p|.
ISSUE(privacycg/storage-access#144): We shouldn't use the permissions task source here.
ISSUE: Shouldn't step 9 be [=same site=]?
<h4 id="ua-policy">User Agent storage access policies</h4>
Different User Agents have different policies around whether or not [=sites=] may access their [=unpartitioned data=] when they're in a [=third party context=]. User Agents check and/or modify these policies when client-side storage is accessed (see [[#storage]]) as well as when {{Document/hasStorageAccess()}} and {{Document/requestStorageAccess()}} are called.
To <dfn type="abstract-op">determine if a site has storage access</dfn> with [=partitioned storage key=] |key| and {{Document}} |doc|, run these steps:
1. Let |map| be the result of [=obtain the storage access map|obtaining the storage access map=] for |doc|.
1. Let |flag set| be the result of [=obtain a storage access flag set|obtaining the storage access flag set=] with |key| from |map|.
1. If |flag set|'s [=has storage access flag=] is set, return true.
1. Return false.
To <dfn type="abstract-op">determine the storage access policy</dfn> for [=partitioned storage key=] |key| with {{Document}} |doc| and {{Promise}} |p|, run these steps:
1. Let |map| be the result of [=obtain the storage access map|obtaining the storage access map=] for |doc|.
1. Let |flag set| be the result of [=obtain a storage access flag set|obtaining the storage access flag set=] with |key| from |map|.
1. Let |implicitly granted| and |implicitly denied| (each a [=boolean=]) be the result of running an [=implementation-defined=] set of steps to determine if |key|'s [=partitioned storage key/embedded origin=]'s request for storage access on |key|'s [=partitioned storage key/top-level site=] should be granted or denied without prompting the user.
1. Let |global| be |doc|'s [=relevant global object=].
1. If |implicitly granted| is true, [=queue a global task=] on the [=permissions task source=] given |global| to [=/resolve=] |p|, and return.
1. If |implicitly denied| is true, [=queue a global task=] on the [=permissions task source=] given |global| to [=/reject=] |p| with a "{{NotAllowedError}}" {{DOMException}}, and return.
1. Let |permissionState| be the result of [=requesting permission to use=] "<a permission><code>storage-access</code></a>".
1. If |permissionState| is "granted", [=queue a global task=] on the [=permissions task source=] given |global| to [=/resolve=] |p|, and return.
1. Unset |flag set|'s [=has storage access flag=].
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}}.
<h3 id="navigation">Changes to navigation</h3>
Before changing the current entry of a session history, run the following steps:
1. Let |doc| be current entry's {{Document}}.
1. Let |map| be the result of [=obtain the storage access map|obtaining the storage access map=] for |doc|'s [=Document/browsing context=]'s [=top-level browsing context=].
1. Let |key| be the result of [=generate a partitioned storage key|generating a partitioned storage key=] from |doc|.
1. If |key| is failure, abort these steps.
1. Let |flag set| be the result of [=obtain a storage access flag set|obtaining the storage access flag set=] with |key| from |map|.
1. Unset |flag set|'s [=has storage access flag=].
ISSUE(privacycg/storage-access#3): What this section should look like ultimately hinges on
ISSUE(privacycg/storage-access#137): Add links to current entry and session history to reflect the [navigation and session history rewrite](https://github.com/whatwg/html/pull/6315).
<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 running the [=determine if a site has storage access=] algorithm 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 run [=determine if a site has storage access=] and pass this information 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 an [=/origin=] <dfn for="permission key">requester</dfn>.
ISSUE(privacycg/storage-access#147): Note that this will likely change to a (site, site) keying.
</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 |settings|' [=top-level site=].
1. Let |embeddedOrigin| be |settings|' [=environment settings object/origin=].
1. Return (|topLevelSite|, |embeddedOrigin|).
</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 [=same site=] with |key2|'s [=permission key/top-level=], return false.
1. If |key1|'s [=permission key/requester=] is not [=same origin=] 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 [=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 |top-level site| 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/).