/basket

Basket is a way to create a new JavaScript context within the browser seperate from the global for sandboxing.

Primary LanguageJavaScript

Basket

Basket is a simple method for creating a new JavaScript context in the browser, e.g. for loading external scripts into a page without affecting the global scope of the document. This is useful when there is a need to avoid collisions between otherwise conflicting scripts and to keep the global namespace clean - e.g. when including third-party JavaScript widgets in a web page (see the Sqwidget project, which started the original development in the form of Sandie).

Scripts are sandboxed by loading them inside the window of a temporary iframe, and then returned to a callback function, so that they can be used elsewhere on the main document.

Who to blame

How to use

Just use an HTML script tag:

<script src="https://basketjs.netlify.app/basket.js" />

What to do

Take a look at the demo here.

Use cases

  • Allowing widgets to load different versions of the same plugin or JavaScript library
  • Allowing widgets to load different scripts that use the same names for global variables
  • Keeping widgets away from modifying local user data (see "Security" section)

Avoid var collisions between competing scripts

var foo = 'bar';
basket(
    'script.js',            // this example script contains "var foo = 'blah';"
    function(exports) {     // a callback function, containing vars added by the script
        alert(foo);         // 'bar' (from global scope in the main document)
        alert(exports.foo); // 'blah' (from the sandboxed script)
    }
);

Load multiple scripts

basket(
    [ 'script1.js', 'script2.js' ], // multiple script src's
    function(exports) {             // 'exports' is an object, with properties 'foo1' & 'foo2' from the scripts
        alert(exports.foo1 + exports.foo2);
    }
);

Load objects and pass functions into the sandbox

basket(
    [ { foo1: bar }, function() { this.foo2 = 'blah'; }, 'foo3.js' ], // objects, functions and external scripts
    function(exports) {                                               // 'exports' is a key-value object of 'foo1', 'foo2' & 'foo3'
        alert(exports.foo1 + exports.foo2 + exports.foo3);
    }
);

In this example, an object is merged into the global scope of the sandbox (i.e. the iframe window), a function is executed in the scope of the sandbox, and then an external script is loaded.

This is useful, for example, when certain variables and functions are required to be in place before loading an external script.

Eval code in the sandbox

Pass a string of code, contained within a <script></script> wrapper:

basket('<script>var evilThing = "foo";</script>', callback);

Scripts to be eval'ed can also be passed along with the other types of scripts (external scripts, objects and functions), but (for the moment, at least), the eval'ed script should come first:

basket(['<script>var evilThing = "foo";</script>', { foo: 'bar' }, 'example.js'], callback);

This can be utilised, for example, to prevent a malicious script from accessing global properties, by masking the window property:

basket(['<script>var window = {};</script>', 'evil.js'], callback);

Or by masking more fine-grained properties:

basket(['<script>window.parent = null;</script>', 'evil.js'], callback);

See the note on "Security", below.

Optional third argument: persist

By default, the sandbox will clean up after itself once all the scripts have loaded. To keep keep the sandbox open, so that further scripts can be loaded or further manipulations can be made, simply pass boolean true as the third argument.

The callback function can now be used to manipulate the sandbox further, e.g. via the load() method, which takes the same arguments as the initial constructor:

basket(
    'example.js',
    function() {
        this.load('example2.js', function(vars) {
            doSomething(vars);
        });
    },
    true
);

Document additional methods

Until documentation is complete, check out the source code of basket.js to see what's available.

Security

The initial use case for Sandie was to contain potentially messy, but otherwise trusted, third-party scripts. However, it is also possible to contain malicious scripts. E.g. you can prevent a malicious script from accessing global properties, by masking the local window variable:

basket(['<script>var window = {};</script>', 'evil.js'], callback);

In this example, the malicious script will be unable to access the global parent window or the local document. (Disclaimer: this has not yet been rigorously tested; please do share any research you perform in this area).

Some third-party might legitimately want to access the containing window. Such scripts will break when the sandbox is locked down in this way.

A more sophisticated API for bulletproofing a secure sandbox will be added to Sandie in future. (Your patches and pull requests are very welcome).