LIFDing the web

10-21-2011

Locally Isolated Feature Domains for graceful browser feature rollout

How do we gracefully introduce new browser features that require cross-site data storage? We can use addons and extensions, of course, but those require per-browser development and require the user to take an explicit action to trust us with new powers.

It would be far better if we can simply introduce the feature into web content in a gradual way, using cross-browser technology, and add it natively to the browser when we're sure that the design is right.

Libraries intended to add forthcoming browser features have been around for years; the most common name for them is "polyfill" (after the UK name for the product Americans would call "spackle"). The word "shim" is also used in many cases. A typical polyfill, however, is stateless - it provides logic that is missing from the browser, but does not introduce new data persistence or communication features.

The tricky part of deploying a new feature with cross-browser technology is doing it in a way that mirrors the benefits of doing it natively:

  • All code running on the client
  • No dependencies on external services
  • Access to all the user's data from all web content, subject to the user's control
  • Protection from the introduction of untrusted remote code

In the last year or two, a technique that has all of these properties has emerged and been used in a number of projects. At Mozilla, we're using it for BrowserID and the Open Web Apps projects; Google is using it for Belay, and probably in other projects as well. The first use of it that I saw in the wild was in the xAuth project started by Meebo.

Here's how it works:

  • A developer wants to introduce a new browser API. Let's call it window.newFeature.
  • The developer implements the new feature in JavaScript, using HTML5-only APIs, and places it on a specific website; let's say it's newfeature.org.
  • The developer places the trusted parts of the feature in a JavaScript file served up from newfeature.org; for example https://newfeature.org/trusted.js. This code uses local HTML storage to store user data, in the newfeature.org domain.
  • The developer then creates another JavaScript file that is intended to be included by a website to add the new feature - for example https://newfeature.org/include.js.
  • The include script does a couple things:
    1. It checks to see if the new feature is already there, e.g. by looking for the presence of window.newFeature. If the feature is found, it stops immediately and does nothing.
    2. Otherwise, it opens a hidden IFRAME to the trusted JS file provided by the website, and constructs a postMessage channel to it.
    3. It then defines the new method (window.newFeature) and defines the body of it to be a remote-method-invocation through the postMessage channel to the trusted IFRAME.
  • At runtime, messages are securely passed to the trusted JS, which runs JavaScript code in the newfeature.org domain, and has control over its own data access and APIs. Data can be safely passed between domains, under the control of the developer of the new feature.
  • If the new feature needs to open a window, it can create a popup window in its own domain and present user interface elements there. The popup window can communicate back to the hidden IFRAME since it is in the same domain.
  • Websites "opt-in" to the new feature by including the include.js file from the trusted domain; if they don't include the file, the system doesn't touch them at all.
  • If, someday, the feature is picked up by browsers and implemented natively, the feature domain fades from use.

At the Internet Identity Workshop this week, we held a short workshop to try to come up with a name for this technique. The winner was LIFD: Locally-Isolated Feature Domain.

The technique is quite similar to that use by Facebook Connect and other federated data authorization systems, but the goal is different; rather than using the embedded message channel to communicate with a server, we are using it to communicate with local storage. The "feature domain" exists only to create a firewalled sandbox in which the browser will store the user's data.

Threats and Downsides

This technique isn't perfect, of course. It falls short of the native code ideal in a couple significant ways.

  1. Performance: It adds latency to the host webpage, since additional network steps are included during page load. This can be mitigated in part by intelligent caching of the JS files.
  2. Hosting Security: The security of the user's data is entirely subject to how well the developer controls access to, and authenticates reads from, the trusted feature domain. The code should, of course, be served up over SSL. An attacker who can man-in-the-middle the feature domain, or tamper with the JS code hosted on it, could steal all of the user's data.

The best LIFD deployment should counter these problems by making sure the JavaScript that implements the feature is entirely static, and served securely from a fast CDN with long-lived caching.

Calling websites who require the highest security should locally serve the include.js file from their domain, instead of trusting the feature domain to serve it.

The Content Security Proposal, which is moving from Mozilla to an official W3C standardization track very soon, includes ideas that, if adopted, could make this technique even safer. The sandbox attribute could be applied to the hidden IFRAME, with a connect-src of none, to forbid all remote network access from the LIFD feature.

blog comments powered by Disqus