-
Notifications
You must be signed in to change notification settings - Fork 1
/
spec.bs
350 lines (265 loc) · 18 KB
/
spec.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
<pre class="metadata">
Title: Extending Storage Access API (SAA) to non-cookie storage
Shortname: saa-non-cookie-storage
Repository: privacycg/saa-non-cookie-storage
URL: https://privacycg.github.io/saa-non-cookie-storage/
Editor: Ari Chivukula, w3cid 132484, Google https://google.org, arichiv@google.com
Editor: Johann Hofmann, w3cid 120436, Google https://google.com, johannhof@google.com
Abstract: This extends the Storage Access API to enable content in cross-site iframes to request access to first-party data beyond 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>
<pre class=link-defaults>
spec:html; type:dfn; for:site; text:same site
spec:url; type:interface; text:URL
</pre>
<pre class="anchors">
spec: html; urlPrefix: https://html.spec.whatwg.org/
type: dfn
for: html
text: web storage; url: #webstorage
text: sessionStorage; url: #dom-sessionstorage
text: localStorage; url: #dom-localstorage
text: broadcast channel; url: #broadcasting-to-other-browsing-contexts
text: new broadcastchannel; url: #dom-broadcastchannel
text: shared workers; url: #shared-workers-and-the-sharedworker-interface
text: new sharedworker; url: #dom-sharedworker
text: processing model; url: #worker-processing-model
spec: storage-access; urlPrefix: https://privacycg.github.io/storage-access/
type: dfn
for: environment
text: has storage access; url: #environment-has-storage-access
text: unpartitioned data; url: #unpartitioned-data
text: first-party-site context; url: #first-party-site-context
text: third party context; url: #third-party-context
spec: indexed-db; urlPrefix: https://www.w3.org/TR/IndexedDB/
type: dfn
text: indexed database api; url:
spec: web-locks; urlPrefix: https://www.w3.org/TR/web-locks/
type: dfn
text: web locks api; url:
text: locks; url: #dom-navigatorlocks-locks
spec: service-worker; urlPrefix: https://w3c.github.io/ServiceWorker/
type: dfn
text: cache storage; url: #cache-objects
text: caches; url: #global-caches-attribute
spec: storage; urlPrefix: https://storage.spec.whatwg.org/
type: dfn
text: storage manager; url:
spec: file-system; urlPrefix: https://fs.spec.whatwg.org/
type: dfn
text: file system; url:
spec: file-api; urlPrefix: https://www.w3.org/TR/FileAPI/
type: dfn
text: file api; url:
text: createobjecturl; url: #dfn-createObjectURL
text: revokeobjecturl; url: #dfn-revokeObjectURL
spec: cookies; urlPrefix: https://httpwg.org/http-extensions/draft-ietf-httpbis-rfc6265bis.html
type: dfn
text: samesite; url:#name-the-samesite-attribute-2
</pre>
<pre class=biblio>
{
"STORAGE-ACCESS": {
"authors": [
"Benjamin VanderSloot",
"Johann Hofmann",
"Anne van Kesteren"
],
"href": "https://privacycg.github.io/storage-access/",
"publisher": "W3C",
"title": "The Storage Access API"
},
"STORAGE-PARTITIONING": {
"authors": [
"Privacy Community Group"
],
"href": "https://privacycg.github.io/storage-partitioning/",
"publisher": "W3C",
"title": "Client-Side Storage Partitioning"
}
}
</pre>
<section class="non-normative">
<h2 id="intro">Introduction</h2>
<em>This section is non-normative.</em>
The Storage Access API (SAA) 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]]
This specification extends the client-side storage available beyond cookies.
</section>
<h2 id="extending-saa-to-non-cookie-storage">Extending SAA to non-cookie storage</h2>
This specification defines a method to request access to [=unpartitioned data=] beyond just cookies ({{Document/requestStorageAccess(types)}}), and a method to check if cookie access has specifically been granted ({{Document/hasUnpartitionedCookieAccess()}}).
<div class=example>
Alex visits `https://social.example/`. The page sets a some local storage. This local storage has been set in a [=first-party-site context=].
```javascript
window.localStorage.setItem("userid", "1234");
```
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 local storage set previously might or might not be visible depending on User Agent storage access policies.
Script in the <{iframe}> can call |doc|`.`{{Document/requestStorageAccess(types)}} to request access.
```javascript
let handle = await document.requestStorageAccess({localStorage: true});
let userid = handle.localStorage.getItem("userid");
```
</div>
<h3 id="document-changes">Changes to {{Document}}</h3>
<pre class="idl">
dictionary StorageAccessTypes {
boolean all = false;
boolean cookies = false;
boolean sessionStorage = false;
boolean localStorage = false;
boolean indexedDB = false;
boolean locks = false;
boolean caches = false;
boolean getDirectory = false;
boolean estimate = false;
boolean createObjectURL = false;
boolean revokeObjectURL = false;
boolean BroadcastChannel = false;
boolean SharedWorker = false;
};
[Exposed=Window]
interface StorageAccessHandle {
readonly attribute Storage sessionStorage;
readonly attribute Storage localStorage;
readonly attribute IDBFactory indexedDB;
readonly attribute LockManager locks;
readonly attribute CacheStorage caches;
Promise<FileSystemDirectoryHandle> getDirectory();
Promise<StorageEstimate> estimate();
DOMString createObjectURL((Blob or MediaSource) obj);
undefined revokeObjectURL(DOMString url);
BroadcastChannel BroadcastChannel(DOMString name);
SharedWorker SharedWorker(USVString scriptURL, optional (DOMString or SharedWorkerOptions) options = {});
};
partial interface Document {
Promise<boolean> hasUnpartitionedCookieAccess();
Promise<StorageAccessHandle> requestStorageAccess(StorageAccessTypes types);
};
</pre>
A {{StorageAccessHandle}} object has an associated {{StorageAccessTypes}} <dfn for=StorageAccessHandle>types</dfn>.
When invoked on {{Document}} |doc|, the <dfn export method for=Document><code>hasUnpartitionedCookieAccess()</code></dfn> method must run these steps:
1. Return the result of running {{Document/hasStorageAccess()}} on |doc|.
Note:
Now that {{Document/requestStorageAccess(types)}} <span class=allow-2119>can</span> be used to request [=unpartitioned data=] with or without specifically requesting cookies, it <span class=allow-2119>must</span> be made clear that {{Document/hasStorageAccess()}} only returns true if [=first-party-site context=] cookies are accessable to the current document.
As a function name, {{Document/hasUnpartitionedCookieAccess()}} more clearly communicates this.
For now {{Document/hasStorageAccess()}} is not considered deprecated, but that <span class=allow-2119>may</span> be worth taking up in future.
When invoked on {{Document}} |doc|, the <dfn export method for=Document><code>requestStorageAccess(types)</code></dfn> method must run these steps:
1. Let |p| be [=a new promise=].
1. If |types|.{{StorageAccessTypes/all}} is `false` and |types|.{{StorageAccessTypes/cookies}} is `false` and |types|.{{StorageAccessTypes/sessionStorage}} is `false` and |types|.{{StorageAccessTypes/localStorage}} is `false` and |types|.{{StorageAccessTypes/indexedDB}} is `false` and |types|.{{StorageAccessTypes/locks}} is `false` and |types|.{{StorageAccessTypes/caches}} is `false` and |types|.{{StorageAccessTypes/getDirectory}} is `false` and |types|.{{StorageAccessTypes/estimate}} is `false` and |types|.{{StorageAccessTypes/createObjectURL}} is `false` and |types|.{{StorageAccessTypes/revokeObjectURL}} is `false` and |types|.{{StorageAccessTypes/BroadcastChannel}} is `false` and |types|.{{StorageAccessTypes/SharedWorker}} is `false`:
1. [=/Reject=] |p| with an "{{InvalidStateError}}" {{DOMException}}.
1. Return |p|.
1. Let |requestUnpartitionedCookieAccess| be `true` if |types|.{{StorageAccessTypes/all}} is `true` or |types|.{{StorageAccessTypes/cookies}} is `true`, and `false` otherwise.
1. Let |accessPromise| be the result of running [=request storage access=] with |doc| with |requestUnpartitionedCookieAccess|.
1. If |accessPromise| [=/rejects=] with `reason` |r|:
1. [=/Reject=] |p| with |r|.
1. Else:
1. Let |handle| be a new object of type {{StorageAccessHandle}}.
1. Set |handle|'s [=StorageAccessHandle/types=] to |types|.
1. [=/Resolve=] |p| with |handle|.
1. Return |p|.
<h3 id="request-storage-access-changes">Changes to {{Document/requestStorageAccess()}}</h3>
Redefine {{Document/requestStorageAccess()}} to:
1. Return the result of running [=request storage access=] with |doc| and |requestUnpartitionedCookieAccess| being `true`.
Modify {{Document/requestStorageAccess()}} to instead be the algorithm <dfn export>request storage access</dfn> which takes a {{Document}} |doc| and a `boolean` argument |requestUnpartitionedCookieAccess|.
Modify {{Document/requestStorageAccess()}} at step 14.1.1.1.1 to read:
1. If |requestUnpartitionedCookieAccess| is `true`, then set <var ignore='monkeypatch'>global</var>'s [=environment/has storage access=] to true.
<h3 id="storage">Changes to various client-side storage mechanisms</h3>
For all of the following getters and methods, consider the following modifications:
1. When attempting to [=obtain a storage key for non-storage purposes=] the returned key will use [[STORAGE-PARTITIONING#relaxing-additional-keying]] if the tuple does not simply contain an [=/origin=].
Issue(19): Clarify client-side storage mechanism changes in more detail.
<h4 id="dom-storage">[=Web storage=]</h4>
The <dfn export attribute for=StorageAccessHandle><code>sessionStorage</code></dfn> getter steps are:
1. If |this|'s |types|.{{StorageAccessTypes/all}} is `false` and |this|'s |types|.{{StorageAccessTypes/sessionStorage}} is `false`:
1. Throw an "{{InvalidStateError}}" {{DOMException}}.
1. Return the result of running [=html/sessionStorage=].
The <dfn export attribute for=StorageAccessHandle><code>localStorage</code></dfn> getter steps are:
1. If |this|'s |types|.{{StorageAccessTypes/all}} is `false` and |this|'s |types|.{{StorageAccessTypes/localStorage}} is `false`:
1. Throw an "{{InvalidStateError}}" {{DOMException}}.
1. Return the result of running [=html/localStorage=].
<h4 id="indexed-db">[=Indexed Database API=]</h4>
The <dfn export attribute for=StorageAccessHandle><code>indexedDB</code></dfn> getter steps are:
1. If |this|'s |types|.{{StorageAccessTypes/all}} is `false` and |this|'s |types|.{{StorageAccessTypes/indexedDB}} is `false`:
1. Throw an "{{InvalidStateError}}" {{DOMException}}.
1. Return the result of running {{WindowOrWorkerGlobalScope/indexedDB}}.
<h4 id="web-locks">[=Web Locks API=]</h4>
The <dfn export attribute for=StorageAccessHandle><code>locks</code></dfn> getter steps are:
1. If |this|'s |types|.{{StorageAccessTypes/all}} is `false` and |this|'s |types|.{{StorageAccessTypes/locks}} is `false`:
1. Throw an "{{InvalidStateError}}" {{DOMException}}.
1. Return the result of running [=/locks=] on {{Navigator}}.
<h4 id="cache-storage">[=Cache Storage=]</h4>
The <dfn export attribute for=StorageAccessHandle><code>caches</code></dfn> getter steps are:
1. If |this|'s |types|.{{StorageAccessTypes/all}} is `false` and |this|'s |types|.{{StorageAccessTypes/caches}} is `false`:
1. Throw an "{{InvalidStateError}}" {{DOMException}}.
1. Return the result of running [=/caches=].
<h4 id="file-system">[=File System=]</h4>
When invoked on {{StorageAccessHandle}} |handle| with {{StorageAccessTypes}} |types|, the <dfn export method for=StorageAccessHandle><code>getDirectory()</code></dfn> method must run these steps:
1. Let |p| be [=a new promise=].
1. If |types|.{{StorageAccessTypes/all}} is `false` and |types|.{{StorageAccessTypes/getDirectory}} is `false`:
1. [=/Reject=] |p| with an "{{InvalidStateError}}" {{DOMException}}.
1. Let |directoryPromise| be the result of running {{StorageManager/getDirectory()}} on {{Navigator}}.{{NavigatorStorage/storage}}.
1. If |directoryPromise| [=/rejects=] with `reason` |r|:
1. [=/Reject=] |p| with |r|.
1. Else if |directoryPromise| [=/resolves=] with {{FileSystemDirectoryHandle}} |f|:
1. [=/Resolve=] |p| with |f|.
1. Return |p|.
<h4 id="storage-manager">[=Storage Manager=]</h4>
When invoked on {{StorageAccessHandle}} |handle| with {{StorageAccessTypes}} |types|, the <dfn export method for=StorageAccessHandle><code>estimate()</code></dfn> method must run these steps:
1. Let |p| be [=a new promise=].
1. If |types|.{{StorageAccessTypes/all}} is `false` and |types|.{{StorageAccessTypes/estimate}} is `false`:
1. [=/Reject=] |p| with an "{{InvalidStateError}}" {{DOMException}}.
1. Let |estimatePromise| be the result of running {{StorageManager/estimate()}} on {{Navigator}}.{{NavigatorStorage/storage}}.
1. If |estimatePromise| [=/rejects=] with `reason` |r|:
1. [=/Reject=] |p| with |r|.
1. Else if |estimatePromise| [=/resolves=] with {{StorageEstimate}} |e|:
1. [=/Resolve=] |p| with |e|.
1. Return |p|.
<h4 id="file-api">[=File API=]</h4>
When invoked on {{StorageAccessHandle}} |handle| with {{StorageAccessTypes}} |types| and {{Blob}} or {{MediaSource}} |obj|, the <dfn export method for=StorageAccessHandle><code>createObjectURL(obj)</code></dfn> method must run these steps:
1. If |types|.{{StorageAccessTypes/all}} is `false` and |types|.{{StorageAccessTypes/createObjectURL}} is `false`:
1. Throw an "{{InvalidStateError}}" {{DOMException}}.
1. Return the result of running [=/createObjectURL=] on {{URL}} with |obj|.
When invoked on {{StorageAccessHandle}} |handle| with {{StorageAccessTypes}} |types| and {{DOMString}} |url|, the <dfn export method for=StorageAccessHandle><code>revokeObjectURL(url)</code></dfn> method must run these steps:
1. If |types|.{{StorageAccessTypes/all}} is `false` and |types|.{{StorageAccessTypes/revokeObjectURL}} is `false`:
1. Throw an "{{InvalidStateError}}" {{DOMException}}.
1. Return the result of running [=/revokeObjectURL=] on {{URL}} with |url|.
<h4 id="broadcast-channel">[=Broadcast Channel=]</h4>
When invoked on {{StorageAccessHandle}} |handle| with {{StorageAccessTypes}} |types| and {{DOMString}} |name|, the <dfn export method for=StorageAccessHandle><code>BroadcastChannel(name)</code></dfn> method must run these steps:
1. If |types|.{{StorageAccessTypes/all}} is `false` and |types|.{{StorageAccessTypes/BroadcastChannel}} is `false`:
1. Throw an "{{InvalidStateError}}" {{DOMException}}.
1. Return the result of running [=new BroadcastChannel=] with |name|.
<h4 id="shared-worker">[=Shared Workers=]</h4>
Modify [=Shared Workers=] to define the following:
<pre class="idl">
enum SameSiteCookiesType { "all", "none" };
dictionary SharedWorkerOptions : WorkerOptions {
SameSiteCookiesType sameSiteCookies;
};
</pre>
The default {{SharedWorkerOptions/sameSiteCookies}} is {{SameSiteCookiesType/all}} in [=first-party-site context=] and {{SameSiteCookiesType/none}} otherwise.
Modify {{SharedWorkerGlobalScope}} to have an associated {{SameSiteCookiesType}} <dfn export for=SharedWorkerGlobalScope>sameSiteCookies</dfn>.
Modify [=new SharedWorker=] to accept {{SharedWorkerOptions}} instead of {{WorkerOptions}}.
Modify [=new SharedWorker=] to add a new step below step 1 as follows:
2. If |options|.{{SharedWorkerOptions/sameSiteCookies}} is {{SameSiteCookiesType/all}} and {{Window}}'s [=associated document=] is not [=first-party-site context=], then:
1. Throw an "{{InvalidStateError}}" {{DOMException}}.
Modify [=new SharedWorker=] to add a new matching criteria in step 10.2.2 as follows:
* <var ignore='monkeypatch'>scope</var>'s |sameSiteCookies| equals |options|.{{SharedWorkerOptions/sameSiteCookies}}.
Modify [=Processing Model=] to add a new step below step 10.4 as follows:
5. Set <var ignore='monkeypatch'>worker global scope</var>'s |sameSiteCookies| to |options|.{{SharedWorkerOptions/sameSiteCookies}}.
Note:
The {{SameSiteCookiesType}} is used to influence which cookies are sent or read during [=fetch=] based on the [=SameSite=] cookie attribute.
{{SameSiteCookiesType/all}} is only available in [=first-party-site context=] and permits [=SameSite=] "None", "Lax", and "Strict" cookies to be included (if not blocked for some other reason).
{{SameSiteCookiesType/none}} is available in any context and permits only [=SameSite=] "None" cookies to be included (if not blocked for some other reason).
Issue(21): Clarify SharedWorker usage of {{SharedWorkerOptions/sameSiteCookies}} in more detail.
When invoked on {{StorageAccessHandle}} |handle| with {{StorageAccessTypes}} |types|, {{USVString}} |scriptURL|, and {{DOMString}} or {{SharedWorkerOptions}} |options|, the <dfn export method for=StorageAccessHandle><code>SharedWorker(scriptURL, options)</code></dfn> method must run these steps:
1. If |types|.{{StorageAccessTypes/all}} is `false` and |types|.{{StorageAccessTypes/SharedWorker}} is `false`:
1. Throw an "{{InvalidStateError}}" {{DOMException}}.
1. Return the result of running [=new SharedWorker=] with |scriptURL| and |options|.
<h2 id="privacy">Security & Privacy considerations</h2>
In extending an existing access-granting API, care must be taken not to open additional security issues or abuse vectors relative to comprehensive cross-site cookie blocking and storage partitioning.
Except for Service Workers (which will not be supported in this extension) non-cookie storage and communication APIs don't enable any capability that could not be built with cookie access alone.
For more detailed discussions see [[STORAGE-ACCESS#privacy]] and [[STORAGE-ACCESS#security]].