content/browser/fenced_frame/SANDBOX_FLAGS.md
A fenced frame is an embedded context that enforces strict boundaries between
itself and its embedder. Fenced frames are not allowed to communicate
information to and from their embedders (i.e. using something like
window.postMessage()), nor are they allowed to learn information about their
context that can help them form a fingerprint of where they're embedded.
This document serves as an audit for every sandbox flag to determine whether it's safe to enable inside of a fenced frame. To determine that, we need to answer the following questions:
If you're adding a new sandbox flag, please do the following:
Please feel free to reach out to
third_party/blink/renderer/core/html/fenced_frame/OWNERS with any questions
you have.
Protected Audience-created fenced frames have the privacy guarantee that no information can flow from the embedder into the fenced frame. If we allowed arbitrary sandboxing flag sets to be applied, an embedder can enable and disable a specific combination of flags, which the fenced frame can then test for to create a fingerprint of the context it's in, allowing up to 1 bit per flag (up to 15 bits, as of January 2025) to be infiltrated into the fenced frame.
To mitigate this risk, we require a specific set of sandbox flags to be unsandboxed in the context the fenced frame loads in. If any of those flags are sandboxed (i.e. restricted), the fenced frame will not load. The rest of the flags are forced to be sandboxed once the fenced frame loads, even if the fenced frame attempts to enable the flag.
See: kFencedFrameForcedSandboxFlags and
kFencedFrameMandatoryUnsandboxedFlags in
third_party/blink/public/common/frame/fenced_frame_sandbox_flags.h. The forced
sandboxed and mandatory unsandboxed flags are the current behavior for all
fenced frames variants.
selectURL-created fenced frames can contain information from the embedder by having the embedder add arbitrary data to the URLs that the frame is navigated to. Because of this, it is acceptable to have information flow in from the embedder to the fenced frame via the combination of sandboxing flags in place.
However, stopping data outflow from the fenced frame to the embedder is still part of the privacy story. If unsandboxing a flag results in access to an API that can exfiltrate data across a fenced frame boundary, that flag must always be forced to be sandboxed.
When fenced frames get access to unpartitioned data, they also lose network
access. This is done to prevent the unpartitioned data from being exfiltrated
and joined with other cross-site signals. After this point, the unpartitioned
data must not be able to be sent out of the fenced frame. Otherwise, a frame
that still has network access can act as a proxy to send the data to an external
server. Note that these fenced frames can be created with the
FencedFrameConfig(url) constructor, which allows arbitrary data to flow in via
the URL.
Below is an audit of all sandbox flags (as of January 2025) and whether enabling
them for fenced frames (that can allow embedder information to be transferred to
the FF, i.e. FencedFrameConfig(url) and selectURL()-created frames), if
needed in the future, is safe or will pose a risk:
Legend:
Note that since Protected Audience-created fenced frames are used specifically for ads, much of the UX concerns listed in this document are from an ads perspective.
Sandbox flag: allow-downloads
All downloads are explicitly disabled in fenced frames regardless of sandbox flags. An ad that automatically starts a download is something we want to avoid, since that can be used for abuse vectors. Allowing this flag will have no effect inside of a fenced frame.
Sandbox flag: allow-forms
Forms allow data to be POSTed to external servers. However, the embedding context has no knowledge of this happening. Since forms are entirely self-contained within the fenced frame, no new information can be infiltrated into the fenced frame, and the form can't be used to learn about the embedding context.
Sandbox flag: allow-modals
Modals (such as alert(), confirm(), and print()) are explicitly disallowed
in fenced frames. Allowing an ad to open an alert box will be extremely
irritating to the user, regardless of whether the user gave the frame user
activation. This flag will and should have no effect inside of a fenced frame.
Sandbox flag: allow-orientation-lock
Orientation lock is
deprecated
because allowing subframes to lock the orientation of an entire device's screen
rotation can easily be confusing and frustrating for users. The replacement,
orientation.lock(), is also explicitly disallowed in fenced frames for that
same reason. This flag will and should have no effect inside of a fenced frame.
Sandbox flag: allow-pointer-lock
An ad locking the user's pointer is very bad UX, so pointer lock is always disabled in fenced frames regardless of the sandbox flag.
Sandbox flag: allow-popups
Data is sent to external servers in order to open a pop-up window. However, the embedding context has no knowledge of this happening, and servers can only be provided information that already exists in the fenced frame. Once a pop-up is created, it will follow a no-opener relationship with the fenced frame and not be able to send any data back to it. Any potential risks with unpartition data access are mitigated simply by the network revocation mechanism preventing any network requests from going out after the fenced frame gets access.
Sandbox flag: allow-popups-to-escape-sandbox
This is currently forced to be unsandboxed. If we allow this to be sandboxed, then a pop-up could inherit the sandboxing flag set of the fenced frame, using that to build a fingerprint of the context that opened it. However, the same amount of information can be conveyed simply by passing in query parameters to the URL. For that reason, there is no additional risk allowing this restriction introduces.
Sandbox flag: allow-presentation
Information about display availability for presentations is available across contexts. If one context enters presentation mode, that can be observed by another context. Therefore, this must not be allowed inside of fenced frames.
Sandbox flag: allow-same-origin
This flag is currently forced to be unsandboxed. The fenced frame getting information about its origin does not pose any risk since fenced frames cannot access origin scoped storage. Their storage, cookies, and network access is further guaranteed to be ephemeral via a nonce in the corresponding storage, cookie, and network isolation keys as described in this explainer.
Sandbox flag: allow-scripts
While there exist APIs that are called via JavaScript that are able to leak information across fenced frame boundaries, disabling all scripts for all fenced frames would be too drastic of a move and would render fenced frames useless. Instead, our approach is to tackle the offending APIs directly, disallowing them within fenced frames rather than all script calls, problematic or otherwise.
Sandbox flag: allow-storage-access-by-user-activation
The Storage Access API allows subframes to get access to unpartitioned third party cookies, an explicit non-goal of fenced frames. Allowing this will open an unrestricted communication channel across fenced frame boundaries.
Sandbox flag: allow-top-navigation-by-user-activation
For most use cases, no information can be exfiltrated that can't already be
exfiltrated via a fetch() request.
Fenced frames created with
selectURL()
can have up to 3 bits of cross-site data per navigation (since selectURL()
takes up to 8 URLs as input) which can be leaked as part of a top-level
navigation. However, there are mitigations in place in the Shared Storage API to
minimize this risk and make this acceptable to enable in fenced frames.
See: https://github.com/WICG/shared-storage/blob/main/select-url.md#privacy
Sandbox flag: allow-top-navigation
This has the same risks as allow-top-navigation-by-user-activation, but now
there does not need to be a user gesture to send the information out. Because
this can now happen automatically, this is more of a risk for
selectURL()-generated fenced frames.
Allowing fenced frames to open pop-ups without ever getting any user interaction can result in a bad user experience. This should not be allowed. even in cases where no information can be leaked.
Sandbox flag: allow-top-navigation-to-custom-protocols
A fenced frame making a request to an http:// or https:// endpoint is no different from a privacy standpoint than a request to an ftp:// or a mailto:// endpoint. Information can be encoded in each protocol's request, and none of them give the fenced frame access to any new information about its context or vice versa.
Sandbox flag: allow-same-site-none-cookies
Fenced frames only allow access to partitioned
cookies.
Allowing SameSite=None cookies allows cookies to be shared between sites and
cross-site requests, but that is only useful in contexts where this kind of
unpartitioned cookie access is allowed to begin with. Since fenced frames force
all cookies to be partitioned regardless, enabling this flag inside a fenced
frame will be a no-op.