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 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 the lifecycle of the global storage access map ? How long do we remember its contents? Firefox and Safari differ here. [Issue #privacycg/storage-access#2]
When do we age out entries in the global storage access map ? See also Scope of Storage Access . [Issue #privacycg/storage-access#5]
Each agent cluster has a storage access map .
When an agent cluster is created, its storage access map is initialized with a clone of the global storage access map .
To
obtain
the
storage
access
map
for
a
Document
doc
,
run
the
following
steps:
-
Return the storage access map of doc ’s relevant agent 's agent cluster .
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 ).
A storage access flag set 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 has storage access flag
-
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 .
The was expressly denied storage access flag When set, this flag indicates that the user expressly denied embedded origin access to its unpartitioned data when it’s loaded in a third party context on top-level site .
To obtain a storage access flag set for a partitioned storage key key from a storage access map map , run the following steps:
-
If map [ key ] does not exist , run these steps:
-
Let flags be a new storage access flag set .
-
Set map [ key ] to flags .
-
-
Return map [ key ].
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"DOMExceptionand 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 .
-
Run these steps in parallel :Letmap be the result of obtaining the storage access map for doc . Let flag sethasAccess be the result ofobtaining the storage access flag set with key from map . If flag set ’s was expressly denied storage access flag is set, queuerunning determine if aglobal task on the permission task source given global to resolve p with false, and abort these steps. If flag set ’ssite has storage accessflag is set, queue a global task on the permission task source given global to resolve p with true, and abort these steps. Let hasAccess be a new promise . Determine the storage access policywith key, docandhasAccessdoc . -
Queue a global task on the
permissionpermissions task source given global to resolve p withthe result ofhasAccess . -
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"DOMExceptionand return p . -
Let global be doc ’s relevant global object .
-
If global is not a secure context , then reject p with a "
NotAllowedError"DOMExceptionand return p . -
If this algorithm was invoked when doc ’s
Windowobject did not have transient activation , reject p with a "NotAllowedError"DOMExceptionand return p . -
If doc ’s browsing context is a top-level browsing context , resolve and return p .
-
If doc is not allowed to use the
"storage-access"permission, reject p with a "NotAllowedError"DOMExceptionand return p . -
If doc ’s origin is an opaque origin , reject p with a "
NotAllowedError"DOMExceptionand return p . -
If the top-level origin of doc ’s relevant settings object is an opaque origin , reject p with a "
NotAllowedError"DOMExceptionand 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"DOMExceptionand return p . -
Let key be the result of generating a partitioned storage key from doc .
-
If key is failure, reject p with a "
NotAllowedError"DOMExceptionand return p . -
Let map be the result of obtaining the storage access map for doc .
-
Let flag set be the result of obtaining the storage access flag set with key from map .
-
If flag set ’s
was expressly denied storage access flag is set, reject p with a " NotAllowedError " DOMException and return p . If flag set ’shas storage access flag is set, resolve and return p . -
Otherwise, run these steps in parallel :
-
Let hasAccess be a new promise .
-
Determine the storage access policy with key , doc and hasAccess .
-
Queue a global task on the
permissionpermissions task source given global to-
Set flag set ’s has storage access flag .
-
Resolve or rejectIfphasAccessbased on the result ofis true, resolvehasAccessp . -
Save the storage access flag set forRejectkeypin map .with a "NotAllowedError"DOMException.
-
-
-
Return p .
We shouldn’t use the permissions task source here. [Issue #privacycg/storage-access#144]
Shouldn’t step 9 be same 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()
and
requestStorageAccess()
are
called.
To
determine
if
a
site
has
storage
access
with
partitioned
storage
key
key
and
Document
doc
,
run
these
steps:
-
Let map be the result of obtaining the storage access map for doc .
-
Let flag set be the result of obtaining the storage access flag set with key from map .
-
If flag set ’s has storage access flag is set, return true.
-
Let has storage access (a boolean ) be the result of running an implementation-defined set of steps to determine if key ’s embedded origin has access to its unpartitioned data on key ’s top-level site . If has storage access is true, set flag set ’s has storage access flag . Save the storage access flag set for key in map .Returnhas storage access .false.
To
determine
the
storage
access
policy
for
partitioned
storage
key
key
with
Document
doc
and
Promise
p
,
run
these
steps:
-
Let map be the result of obtaining the storage access map for doc .
-
Let flag set be the result of obtaining the storage access flag set with key from map .
-
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.
Note: These implementation-defined set of steps might result in flag set ’s has storage access flag and was expressly denied storage access flag changing, since the User Agent could have relevant out-of-band information (e.g. a user preference that changed) that this specification is unaware of. -
Let global be doc ’s relevant global object .
-
If implicitly granted is true, queue a global task on the
permissionpermissions task source given global to resolve p , and return. -
If implicitly denied is true, queue a global task on the
permissionpermissions task source given global to reject p with a "NotAllowedError"DOMException, andreturn p .return. -
Ask the user if they would like to grant key ’s embedded origin access to its unpartitioned data when it’s loaded in a third party context on key ’s top-level site , and wait for an answer.Letexpressly granted and expressly denied (both booleans ) be the result. Note: While expressly granted and expressly deniedpermissionStatecannot bothbetrue, they could both be false in User Agents which allow users to dismisstheprompt without choosingresult of requesting permission toallow or deny the request. (Such a dismissal is interpreted in this algorithm as a denial.)use "storage-access". -
If
expressly grantedpermissionState istrue, run these steps: Unset flag set ’s was expressly denied storage access flag . Save the storage access flag set for key in map . Queue"granted", queue a global task on thepermissionpermissions task source given global to resolve p , and return. -
Unset flag set ’s has storage access flag .
-
If
expressly denied is true, run these steps: Ifdoc ’sWindowobject has transient activation , consume user activation with it. -
Set flag set ’s was expressly denied storage access flag . Save the storage access flag set for key in map .Queue a global task on thepermissionpermissions task source given global to reject p with a "NotAllowedError"DOMException.
3.3. Changes to navigation
Before changing the current entry of a session history, run the following steps:
-
Let doc be current entry’s
Document. -
Let map be the result of obtaining the storage access map for doc ’s browsing context 's top-level browsing context .
-
Let key be the result of generating a partitioned storage key from doc .
-
If key is failure, abort these steps.
-
Let flag set be the result of obtaining the storage access flag set with key from map .
-
Unset flag set ’s has storage access flag .
Save the storage access flag set for key in map .
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
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
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.
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-activationkeyword.
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 aPermissionDescriptorpermissionDesc and aPermissionStatusstatus :Set status ’s
stateto permissionDesc ’s permission state .If status ’s
stateis denied , set status ’sstateto 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.
5.
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
Documentis permitted by the embedder to use the API -
the nested
Documentis 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.
5.1.
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.
6.
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.
6.1.
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.
6.2.
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.
7.
8.
Automation
For the purposes of user-agent automation and application testing, this document defines the following extension command for the [WebDriver] specification.
7.1.
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 .