1. Introduction
This section is non-normative.
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]
2. Infrastructure
This specification depends on the Infra standard. [INFRA]
3. The Storage Access API
This
specification
defines
a
method
to
query
whether
or
not
a
Document
currently
has
access
to
its
unpartitioned
data
(
hasStorageAccess()
),
and
a
method
that
can
be
used
to
request
access
to
its
unpartitioned
data
(
requestStorageAccess()
).
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
.
cookie
,
depending
on
User
Agent
storage
access
policies.
Script
in
the
iframe
can
call
doc
.
hasStorageAccess()
to
determine
if
it
has
access
to
the
cookie.
If
it
does
not
have
access,
it
can
request
access
by
calling
doc
.
requestStorageAccess()
.
Unpartitioned data 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
first-party-site
context
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
origin
and
top-level
origin
of
its
relevant
settings
object
are
same
site
with
one
another.
A
Document
is
in
a
third
party
context
if
it
is
not
in
a
first-party-site
context
.
3.1.
User
Agent
Changes
to
user
agent
state
related
to
storage
access
A
storage
access
map
is
a
map
whose
keys
are
partitioned
storage
keys
and
whose
values
are
storage
access
flag
sets
.
User
Agents
maintain
a
single
global
storage
access
map
.
What
is
Modify
the
lifecycle
definition
of
the
global
storage
access
map
?
How
long
do
we
remember
its
contents?
Firefox
and
Safari
differ
here.
[Issue
#privacycg/storage-access#2]
environment
When
do
we
age
out
entries
in
the
global
storage
access
map
?
See
also
Scope
of
Storage
Access
.
[Issue
#privacycg/storage-access#5]
following
manner:
-
Each agent cluster hasAdd astorage access map . When an agent cluster is created, itsnew member called has storage accessmap is initialized with a cloneofthe global storage access maptype boolean .
To
obtain
Modify
the
storage
access
map
for
a
Document
definition
of
source
snapshot
params
doc
,
run
in
the
following
steps:
manner:
-
Return theAdd a new member called has storage accessmapofdoc ’s relevant agent 's agent clustertype boolean . Add a new member called environment id of type opaque string .
A partitioned storage key is a tuple consisting of a top-level site (a site ) and an embedded origin (an origin ).
(("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)
.
To
generate
a
partitioned
storage
key
for
a
Document
doc
,
run
the
following
steps:
-
Let settings be doc ’s relevant settings object .
-
Let site be the result of obtaining a site from settings ’ origin .
-
If doc ’s browsing context is a top-level browsing context , return the partitioned storage key ( site , site ).
-
Let top-level site be the result of obtaining a site from settings ’ top-level origin .
-
Return the partitioned storage key ( top-level site , site ).
3.2.
Changes
to
Document
partial interface Document {Promise <boolean >hasStorageAccess ();Promise <undefined >requestStorageAccess (); };
When
invoked
on
Document
doc
,
the
hasStorageAccess()
method
must
run
these
steps:
-
Let p be a new promise .
-
If doc is not fully active , then reject p with an "
InvalidStateError
"DOMException
and return p . -
If doc ’s origin is an opaque origin , resolve p with false and return p .
-
Let global be doc ’s relevant global object .
-
If global is not a secure context , then resolve p with false and return p .
-
If doc ’s browsing context is a top-level browsing context , resolve p with true and return p .
-
If the top-level origin of doc ’s relevant settings object is an opaque origin , resolve p with false and return p .
-
If doc ’s origin is same origin with the top-level origin of doc ’s relevant settings object , resolve p with true and return p .
-
Let key be the result of generating a partitioned storage key from doc . If key is failure, resolve p with false and return p . Let hasAccess be the result of running determine if a site has storage access with key and doc .Queue a global task on the permissions task source given global to resolve p withhasAccess .|global’s| has storage access . -
Return p .
Shouldn’t step 8 be same site ?
When
invoked
on
Document
doc
,
the
requestStorageAccess()
method
must
run
these
steps:
-
Let p be a new promise .
-
If doc is not fully active , then reject p with an "
InvalidStateError
"DOMException
and return p . -
Let global be doc ’s relevant global object .
-
If global is not a secure context , then reject p with a "
NotAllowedError
"DOMException
and return p . -
If
this algorithm was invoked when doc ’s Window object did not have transient activation , reject p with a " NotAllowedError " DOMException and return p . Ifdoc ’s browsing context is a top-level browsing context , resolve and return p . -
If doc is not allowed to use
the""storage-access"storage-accesspermission,", reject p with a "NotAllowedError
"DOMException
and return p . -
If doc ’s origin is an opaque origin , reject p with a "
NotAllowedError
"DOMException
and return p . -
If the top-level origin of doc ’s relevant settings object is an opaque origin , reject p with a "
NotAllowedError
"DOMException
and return p . -
If doc ’s origin is same origin with the top-level origin of doc ’s relevant settings object , resolve and return p .
-
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 . -
LetIfkeyglobalbe the result of generating a partitioned’s has storagekeyaccessfrom doc . If keyisfailure, rejecttrue, resolve p witha " NotAllowedError "DOMExceptionundefinedreturn p .return. -
Let
maphas transient activation bethe result of obtaining the storage access map forwhether doc. Let flag set be the result of obtaining the storage access flag set with key from map . If flag set’shas storage access flag is set, resolveWindow
object has transient activation .and return p . -
Otherwise, run theseRun the following steps in parallel :-
Let
hasAccessprocess permission state be an algorithm that, given anew promise . Determine the storage access policypermission statewithkeystate ,doc and hasAccess .runs the following steps:-
Queue a global task on the
permissionspermission task source given globaltoto:If state is granted :
-
Set
flag setglobal ’s has storage accessflag .to true.
-
Else:
Consume user activation given
pglobal .-
Reject p with a "
NotAllowedError
"DOMException
.
-
-
ReturnLetp . We shouldn’t use the permissions task source here. [Issue #privacycg/storage-access#144] Shouldn’t step 9previous permission state besame site ? 3.2.1. User Agent storage access policies 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 § 3.4 Changes to various client-side storage mechanisms ) as well as when hasStorageAccess()the result of getting the current permission state given "storage-access
andrequestStorageAccess() are called. To determine if a site has storage access with partitioned storage keykey" andDocumentdoc , run these steps:global . -
LetIfmapprevious permission statebe the result of obtaining the storage access map for doc .is not prompt :-
LetRunflag setprocess permission statebe the result of obtaining the storage access flag setwithkey from mapprevious permission state . -
Abort these steps.
-
If
flag set ’shasstorage access flagtransient activation isset, return true.false:-
Return false. To determine the storage access policy for partitioned storage keyRunkeyprocess permission state withDocument doc and Promise p , run these steps:denied . -
Let map be the result of obtaining the storage access map for doc .Abort these steps.
-
-
Let
flag setkey be the result ofobtaining thegenerating a partitioned storageaccess flag set withkey frommapdoc . -
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 embedded origin 's request for storage access on key ’s top-level site should be granted or denied without prompting the user.
-
Let global be doc ’s relevant global object .If implicitly granted istrue, queue a global task on the permissions task source giventrue:Run
globalprocess permission stateto resolve p , and return.with granted .Abort these steps.
-
If implicitly denied is
true, queue a global task on the permissions task source given global to rejecttrue:Run
pprocess permission state witha " NotAllowedError " DOMException , and return.denied .-
Abort these steps.
Let permissionState be the result of requesting permission to use "
storage-access
".-
If permissionState is "granted", queue a global task on the permissions task source givenRunglobalprocess permission stateto resolvewithp , and return.permissionState .
-
-
UnsetReturnflag set ’s has storage access flag .p .If doc ’s Window
object
has
transient
activation
,
consume
NOTE:
The
intent
of
this
algorithm
is
to
always
require
user
activation
with
it.
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.
Queue
a
global
task
on
We
shouldn’t
use
the
permissions
task
source
here.
[Issue
#privacycg/storage-access#144]
given
global
to
reject
p
with
a
"
NotAllowedError
"
DOMException
.
Shouldn’t
step
9
be
same
site
?
3.3. Changes to navigation
Before
changing
When
snapshotting
source
snapshot
params
:
Set has storage access to sourceDocument ’s has storage access .
Set environment id to sourceDocument ’s relevant settings object 's id .
To
the
current
entry
of
a
session
history,
run
create
navigation
params
by
fetching
algorithm,
insert
the
following
steps:
step
as
step
3:
-
Let
docoriginalURL becurrent entry’s Documententry ’s URL.
When
creating
request
’s
reserved
client
.
in
create
navigation
params
by
fetching
:
-
LetSet reserved client 's has storage access tomapsourceSnapshotParamsbe the result of obtaining the’s has storage accessmapforif all of the following hold:docsourceSnapshotParams ’sbrowsing contextenvironment id equals navigable ’s active document 'stop-level browsing contextrelevant settings object 's id .-
LetkeyoriginalURLbe the result of generating a partitioned storage key’s originfromis same origin withdoc .currentURL ’s origin . -
Ifkeyresponse isfailure, abort these steps.null or response ’s has-cross-origin-redirects is false.
-
Let flagOtherwise, set requestbe the result of obtaining the’s reserved client 's has storage accessflag setwith key from map .to false.
When setting up a window environment settings object :
-
UnsetSetflag setsettings object ’s has storage access to reserved environment ’s has storage accessflag.
What this section should look like ultimately hinges on [Issue #privacycg/storage-access#3]
Add links to current entry and session history to reflect the navigation and session history rewrite . [Issue #privacycg/storage-access#137]
3.4. Changes to various client-side storage mechanisms
This API only impacts HTTP cookies. A future revision of this API might impact other client-side state. [RFC6265]
3.4.1. Cookies
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
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
accessing
the
determine
if
a
site
environment
's
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
cookie
to
run
determine
if
a
site
pass
the
environment
's
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.
3.5. Sandboxing storage access
A sandboxing flag set has a sandbox storage access by user activation flag . This flag prevents content from requesting storage access.
To the parse a sandboxing directive algorithm, add the following under step 3:
-
The
sandbox
storage
access
by
user
activation
flag
,
unless
tokens
contains
the
allow-storage-access-by-user-activation
keyword.
4. Permissions Integration
The
Storage
Access
API
defines
a
powerful
feature
identified
by
the
name
"
storage-access
".
It
defines
the
following
permission-related
algorithms:
- permission query algorithm
-
To
query
the
"
storage-access
" permission, given aPermissionDescriptor
permissionDesc and aPermissionStatus
status :-
Set status ’s
state
to permissionDesc ’s permission state . -
If status ’s
state
is denied , set status ’sstate
to 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.
-
- permission key type
-
A
permission
key
of
the
"
storage-access
" feature is a tuple consisting of a site top-level and an origin requester .Note that this will likely change to a (site, site) keying. [Issue #privacycg/storage-access#147]
- permission key generation algorithm
-
To
generate
a
new
permission
key
for
the
"
storage-access
" feature, given an environment settings object settings , run the following steps:-
Let topLevelSite be settings ’ top-level site .
-
Let embeddedOrigin be settings ’ origin .
-
Return ( topLevelSite , embeddedOrigin ).
-
- permission key comparison algorithm
-
To
compare
the
permission
keys
key1
and
key2
for
the
"
storage-access
" feature, run the following steps:
5. Permissions Policy Integration
The
Storage
Access
API
defines
a
policy-controlled
feature
identified
by
the
string
"storage-access"
.
Its
default
allowlist
is
"*"
.
Note:
A
Document
’s
permissions
policy
determines
whether
any
content
in
that
document
is
allowed
to
request
storage
access
using
requestStorageAccess()
.
If
disabled
in
any
document,
calling
requestStorageAccess()
in
that
document
will
reject.
6. Privacy considerations
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
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 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:
-
the user interacts with the nested
Document
-
the nested
Document
is permitted by the embedder to use the API -
the nested
Document
is a secure context -
the user grants express, pairwise permission to the embeddee to use its cookies in the embedder
-
the user is not inundated by requests for the "
storage-access
" 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
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.

document.
requestStorageAccess()
.
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.
6.1. Permission scope
Another
tension
in
the
design
of
the
API
is
what
to
use
to
key
the
"
storage-access
"
permission:
origins
or
sites
.
We
chose
sites
because
we
believe
them
to
be
acceptable
boundaries
for
privacy
while
enabling
existing
uses
of
same
site
and
cross-origin
nested
Documents
on
the
same
page
with
only
one
user
prompt.
7. Security considerations
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
"
storage-access
"
permission
grant
to
only
give
access
to
unpartitioned
data
to
the
nested
Document
that
called
requestStorageAccess()
and
only
until
the
nested
Document
navigates
across
an
origin
boundary.
This
ensures
that
only
|origins
with
a
page
that
call
requestStorageAccess()
will
be
making
credentialed
requests,
and
moreover
the
embedee
page
can
control
which
embedder
it
permits
via
the
Content
Security
Policy
"
frame-ancestors
"
directive.
This
retains
an
origin
-scoped
control
for
security
purposes
by
the
embedee.
7.1. Reputational attacks
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
Documents
have
the
ability
to
become
authenticated,
or
even
display
a
permission
request
to
the
user
via
the
Permission
Policy
and
nested
Document
sandboxing.
7.2. Notification abuse
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.
8. Automation
For the purposes of user-agent automation and application testing, this document defines the following extension command for the [WebDriver] specification.
8.1. Set Storage Access
HTTP Method | URI Template |
---|---|
POST | /session/{session id}/storageaccess |
The Set Storage Access extension command modifies the storage access policy for the current browsing context .
The remote end steps are:
-
Let blocked be the result of getting a property from parameters named
blocked
. -
If blocked is not a boolean return a WebDriver error with WebDriver error code invalid argument .
-
Let embedded origin be the result of getting a property from parameters named
origin
. -
If embedded origin is not a single U+002A ASTERISK character (*), then:
-
Let parsedURL be the the result of running the URL parser on embedded origin .
-
If parsedURL is failure, then return a WebDriver error with WebDriver error code invalid argument .
-
Set embedded origin to parsedURL ’s origin .
-
-
If the current browsing context is not a top-level browsing context return a WebDriver error with WebDriver error code unsupported operation .
-
Let doc be the current browsing context 's active document .
-
Let settings be doc ’s relevant settings object .
-
Let top-level site be the result of obtaining a site from settings ’s origin .
-
If embedded origin is a single U+002A ASTERISK character (*), then:
-
If blocked is
true
, then:-
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 .
-
-
Otherwise, if blocked is
false
, then:-
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 .
-
-
-
Otherwise:
-
If embedded origin is same site with top-level site return a WebDriver error with WebDriver error code unsupported operation .
-
If blocked is
true
, then:-
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 .
-
-
Otherwise, if blocked is
false
, then:-
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 .
-
-
-
If the above implementation-defined step of steps resulted in failure, return a WebDriver error with WebDriver error code unknown error .
-
Return success with data
null
.
Acknowledgements
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 , privacycg/proposals#2 , and privacycg/storage-access/issues for their feedback on this proposal.
Thanks to the WebKit Open Source Project for allowing us to use the Storage Access API Prompt image, which was originally published on webkit.org .