Proposal: Sandbox Policy


#1

TL;DR: The existing HTML spec provides sandboxing capability that restricts abilities of iframed content. We would like to propose extending this:

  1. Allow the policy to be applied to the top-level document with granular opt-in
  2. Allow the top-level policy set in (1) to propagate to subframes
  3. Allow the policy to run in report-only and enforce+report modes
  4. 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:

  1. 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.
  1. 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”.
  • 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:

  1. The first script tag (broken.com/cart.js) would be ignored by the browser because it is a synchronous script
  2. 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).
  3. The request header communicates the policy set by the embedder.
  4. If the response script attempts to use document.write or synchronous XHR within it would fail.
  5. 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)

#2

This looks like a good idea. It would be a good tool for good-citizen web developers to opt-out of dubious and difficult features like document.write.

I have two questions:

  1. This seems to sort of overlap with CSP in the spirit of locking down what a page can do. In fact CSP actually has a sandbox directive as far as I can tell, but apparently can’t be specified with a <meta> element. So could this not be implemented as part of that CSP directive? In particular things like CSP’s referrer directive seems to overlap with what this separate sandbox mechanism would specify, and the no-plugins sandbox directive appears to directly reimplement the CSP object-src 'none' directive. Should there be two mechanisms for this?

  2. Could this be extended to enforce other “good practice” or common development approaches, similar to how there’s a flag to turn off sync XHR? E.g. with some new flags like:

  • force-strict-mode: treat all scripts as if they had "use strict"; at the top, eliminating “sloppy mode” from the whole document
  • top-level-only for a browser-sanctioned way of preventing the page being iframed
  • no-sync-storage to disable local/session storage (the old synchronous APIs superseded by IndexedDB) - the Chrome Web Store already implements this
  • no-prefixed-features - disable all prefixes, forcing standards-compliant uses only
  • no-nonstandard-features - similar to no prefixes, but to turn off all features not actually in the spec, e.g. window.orientation (replaced by screen.orientation), node.innerText (only exists for compatibility reasons), etc.

In particular I think disabling prefixes and nonstandard features could help with ensuring content is standards-compliant and will work in other browsers.


#3

+1 to the idea!

If specified in a <meta>, does it have similar restrictions to charset declarations? That is, must be in the first kb or two of the page’s content? If not, how do you handle a situation like:

<!doctype html>
<script>
doc.write(...);
</script>
<meta http-equiv="Sandbox-Policy" content="no-docwrite">

Or, similarly, what happens if a script doc.write()s a <meta> declaring that doc.write isn’t allowed? :slight_smile:


#4

They are similar in spirit and some features (e.g. no-docwrite) may have security benefits as well. On the other hand we have directives that are not security related (e.g. no-sync-scripts, no-sync-xhr), and I expect that in the future we’ll see more of these – i.e. directives that focus on disabling performance/UX/etc footguns. That said, I do agree that we should not duplicate functionality: if it makes sense to adopt some parts of this proposal under CSP, then we should do so; if CSP already provides some functionality, then we should drop it from Sandbox.

  • Can you elaborate on the overlap with referrer? I don’t follow that one.
  • Re, no-plugins vs CSP object-src: none: that’s a great point, happy drop no-plugins. Less work for us! :slight_smile:

Yes, that’s an interesting list. That said, at least for the initial run, I’d prefer to keep the list reasonably small and focus on the “high value” footguns. Once (and if) we agree on the basic mechanism, and get the plumbing in place, adding new directives is relatively straightforward.

In the same way as we deal with this in CSP: "Authors are strongly encouraged to place meta elements as early in the document as possible, because policies in meta elements are not applied to content which precedes them. "

/me melts into a puddle…


#5

I think I was just confused by the overlap between CSP and this proposal. I was wondering if you’d add a referrer sandbox policy, but I see that you would not if CSP already covers it.

Since the sandbox policy can in general affect the way scripts work, perhaps it could be made an error to have the sandbox policy occur after a script?


#6

That’s probably reasonable, but CSP’s wording also covers this - the <meta>, having been written after the <script>, doesn’t apply to the script. :slight_smile:


#7

“Subframe inherits parent’s policy” us not a sane default for the behavior of an iframe. AMP may have different goals for the web, but iframe is an element with a standard and a purpose, and there’s a near 100% chance of invalidating that original purpose (e.g. sandboxing untrusted but still functional content onto your page) as the default behavior.

Not to mention the conflict, what if the iframe has a meta?


#8

The purpose of this proposal is to limit the possibilities of an iframe to hurt the overall performance and user experience of the embedding site, making it safer (from a UX perspective) for a site to include third party frames. Frames inheriting that policy is the whole point. The AMP project is just one possible client of this type of restriction, but hardly the only use case.

Not sure what you mean there. An iframe is a nested browsing context and it will continue to be that, just with a few extra restrictions regarding what APIs it can call.

If the iframe contains further sandbox restrictions itself, the will add up to the restrictions it inherited from the main page, to create a larger set of restrictions that will apply to it and to whatever browsing context it may embed.


#9

A quick update on the status and where we’re heading…

After circulating the original proposal we’ve spent a few weeks trying to understand the intersection and interop between Sandbox and HTML (iframe) sandbox, CSP sandbox, and the new document features disable + Permission Delegation proposals.

Long story short, we decided that it makes sense to merge the “document features disable” and Sandbox into a single mechanism, which we’re now calling “Feature Policy”.

@mikewest and myself have started iterating on the spec. You can see the skeleton here: https://igrigorik.github.io/feature-policy/#introduction

Take a look at the examples for the high level overview and the “Disable policy features” section for some concrete examples of how they would (likely) be enforced. There are still many gaps to fill in, but there should be enough there to paint the high-level picture.

Let me know if you have any thoughts or comments: open an issue on the GitHub repo and/or PR your suggestions.


p.s. If you want to follow along, I’ve also kicked off this thread to discuss enforcement strategies for various APIs.


#10

Update: we’re actively iterating on Feature-Policy and we sent out and intent to implement earlier today.

@yoavweiss @cwilso could we transfer igrigorik/feature-policy to WICG? It’d be great to get more eyeballs on it.


#11

Definitely! :slight_smile:

Permissions to transfer the repo on their way