TL;DR: The existing HTML spec provides sandboxing capability that restricts abilities of iframed content. We would like to propose extending this:
- Allow the policy to be applied to the top-level document with granular opt-in
- Allow the top-level policy set in (1) to propagate to subframes
- Allow the policy to run in report-only and enforce+report modes
- Extend existing sandbox flags with additional restrictions:
- no-sync-script
- no-sync-xhr
- no-docwrite
Note: this proposal is an iteration on a subset of the functionality proposed by CPP; it’s not the goal of this proposal to address all of the points raised by CPP; it’s not a competing proposal.
Overview
We want to give the developers a mechanism to modify the behavior of, or disable, certain web platform features within their application. By doing so, developers can assert a promise about the use—or lack of thereof—of such features within their application, which enables:
- Developers to lock down their application against regressions caused by own and third party resources; it enables violation auditing and reporting against specified standards.
- Third parties to validate enforced policies and use them as signals about runtime/UX/performance characteristics of the content.
Motivation
The web-platform provides an ever-expanding set of features and APIs, offering richer functionality, better developer ergonomics, improved performance and so on. However, the web-platform has limited ability to deprecate features or some parts of their behavior: full removal of a feature or a change in its behavior will often inevitably break some sites, which makes it nearly impossible to determine potential negative impact of such change. As a result, the platform tends to maintain both the old and the new API’s, even in cases where the new API offers better functionality and performance.
A well-versed developer can avoid the use of API’s that impose unwanted behaviors (e.g. block processing or rendering of the application) through careful design and audits. However, today, the developer is unable to assert a promise about the use—or lack of thereof—of such features to the browser, which results in the following problems:
- The application developer can carefully craft their code to avoid use of costly and unwanted API’s and behaviors—e.g. avoid use of parser-blocking scripts, render-blocking fonts, synchronous touch event handlers, and so on—but is unable to “lock down” their application and enforce it as a policy, to protect the application against various regressions:
- Other developers on their extended team from (un)knowingly or accidentally introducing behaviors that regress their application.
- Third party content executing within their application from introducing behaviors that regress their application.
- It is impossible to infer upfront that certain features and behaviors are unnecessary or unwanted by the application. As a result:
- The browser is unable to modify its behavior, which prevents certain types of “fast path” optimizations.
- External embedders (e.g. social, search, etc, applications) cannot infer properties of the content being linked to, and in absence of such guarantees are compelled to create own formats or frameworks to constrain the developer and to obtain the required guarantees about the structure and performance of content – e.g. Facebook Instant Articles, Apple News, Accelerated Mobile Pages, and so on.
“Sandbox Policy”, which builds on the existing sandboxing functionality, could resolve some of the above problems by allowing the developer to declare, upfront, a policy about the use—or lack of thereof—of some features. For example…
<html>
...
<meta http-equiv="Sandbox-Policy"
content="no-auxiliary-contexts; no-modals;
no-sync-xhr; no-sync-script;
no-docwrite">
<script src="//broken.com/cart.js"> <!-- ignored -->
<script src="//other.com/widget.js" async> <!-- processed -->
<!-- subframes inherit parents sandbox policy by default -->
<iframe src="//another.com/thing"></iframe>
<script>
var request = new XMLHttpRequest();
request.open('GET', '/bar/foo.txt', false);
request.send(null); // error, due to no-sync-xhr
document.write("..."); // error, due to no-docwrite
</script>
...
The above sandbox policy declares that the application opts out from use of synchronous XHR’s, synchronous scripts, document.write
, use of modal dialogs (window.{alert,confirm,print,prompt}
, beforeunload
), and will not open any auxiliary browsing contexts (via target
attribute or window.open()
). In turn, the browser should throw an error if such an API is used, or ignore the directive (e.g. synchronous script tag in the markup).
- The above policy would be applied to the top-level document, and can be set via a element or the equivalent response header.
- The above policy would be propagated down to all subresources of the document.
- The policy is communicated to all subresources, at request time, via a
Sandbox-Policy
request header.- E.g. a script request to a third party will contain a header advertising the policy set by the page, allowing it to ensure that it conforms to the set requirements and is able to execute.
- A subresource is allowed to declare own policy (e.g. via response header), but the policy MUST be more restrictive a superset of the embedder’s policy (i.e. more restrictive)
- E.g. the top-level document can enforce a “
no-sync-xhr
” policy, but the subresource is allowed to declare a “no-sync-xhr; no-docwrite
”.
- E.g. the top-level document can enforce a “
- The policy is communicated to all subresources, at request time, via a
- Optionally, the policy can be configured to run in report-only mode, as a means to audit and detect violations, prior to its enforcement. Or, can be configured to enforce and report violations to a designated reporting endpoint - i.e. same as CSP.
As a result, in above example, if the policy is enforced:
- The first script tag (broken.com/cart.js) would be ignored by the browser because it is a synchronous script
- The second script (other.com/widget.js) would be fetched, with an additional request header advertising the set policy (
Sandbox-Policy: no-auxiliary-contexts; no-modals; no-sync-xhr; no-sync-script; no-docwrite
). - The request header communicates the policy set by the embedder.
- If the response script attempts to use
document.write
or synchronous XHR within it would fail. - The iframe resource would be fetched (with same
Sandbox-Policy
request header as above) and the same document-wide policy would apply to its subresources.
Plumbing & delta from existing spec
Opt-out vs opt-in
Current sandboxing mechanism follows an opt-out policy: enabling sandbox automatically enables a collection of predefined flags, but the developer can opt-out from some of them via allow-{forms, popups, scripts,…}. This works, but it means that we can’t introduce new flags without putting existing content at compatibility risk. Instead, we’d like to propose an opt-in policy:
- Policy starts as blank
- Developer explicitly declares their opt-in via “no-{feature}”
Opt-in mechanism means we can add new flags at will, without breaking existing deployments.
Initial set of proposed Sandbox-Policy flags
- (new) no-sync-script: disallows use of synchronous, parser-blocking scripts. When this policy is set, such scripts should be ignored by the user agent.
- (new) no-sync-xhr: disallows use of synchronous XHR’s. When this policy is set, the browser should throw an error when a sync XHR request is made.
- (new) no-docwrite: disallows use of document.write. When this policy is set, the browser should throw an error when called.
- no-auxiliary-contexts: prevents content from creating new auxiliary browsing contexts, e.g. using the target attribute or the window.open() method.
-
no-modals: prevents content from using any of the following features to produce modal dialogs:
- window.alert()
- window.confirm()
- window.print()
- window.prompt()
- the beforeunload event
- no-plugins: prevents content from instantiating plugins, whether using the embed element, the object element, the applet element.
Reporting
- To assist developers in deploying sandboxing policies, we need an report mode where they can run the policy in audit-only mode: In this mode, violations do not cause errors but are logged to the specified URI—i.e. identical to CSP report-only mode.
- Once the policy is configured to “enforce”, violations should be reported if a report-uri provided - e.g. to detect violations by own or third party content.
Interop with existing <iframe sandbox>
By default, the sandbox policy should propagate to all subresources and subframes. As such:
- If the iframe does not declare its own sandbox policy, it still inherits embedders policy Embedder can specify a more restrictive policy on an iframe - e.g. top level document enforces no-modals only, but can enforce a stricter set on iframed content.
- The enforced policy on a subframe is always a union of the policies - i.e. if the top-level document enforces no-modals then the subframe also inherits this policy in all cases (i.e. allow-popups is ignored)