npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

xs-sandbox

v1.0.1

Published

JavaScript sandbox with snapshotting

Downloads

6

Readme

XS Sandbox

Secure and easy JavaScript sandbox with heap snapshotting support, with no dependencies or native modules. Compatible with Node.js or browser environment.

Internally uses a WASM build of the XS JavaScript engine (guest scripts are not running in the same engine as the host and so are completely isolated). It's reasonably lightweight -- each instance is a few MB.

Usage: Evaluating Scripts

import Sandbox from 'xs-sandbox';

const sandbox = await Sandbox.create();
const result = sandbox.evaluate('1 + 1');
console.log(result); // 2

Usage: Heap Snapshotting

Heap snapshotting can be used to save the state of the guest and restore it later.

import Sandbox from 'xs-sandbox';

const s1 = await Sandbox.create();
s1.evaluate('var x = 1');
console.log(s1.evaluate('++x')); // 2
console.log(s1.evaluate('++x')); // 3

const snapshot = s1.snapshot(); // snapshot as Uint8Array

// Later, or on another machine
const s2 = await Sandbox.restore(snapshot);
console.log(s2.evaluate('++x')); // 4

An empty snapshot is about 85kB.

Usage: Message passing

import Sandbox from 'xs-sandbox';

const sandbox = await Sandbox.create();

// Receive messages from the sandbox like this:
sandbox.receiveMessage = function (message) {
  console.log(`Received message from sandbox: ${message}`)
}

sandbox.evaluate(`
  // Receive messages from the host like this:
  globalThis.receiveMessage = function (message) {
    // Send messages to the host like this:
    sendMessage('Received message from host: ' + message);
  }
`);

// Send a message to the guest like this:
sandbox.sendMessage('Message from host');

Messages are encoded as JSON to be sent to/from the sandbox so only plain data types are supported.

Messages are passed synchronously. If you want asynchronous behavior you can wrap the sandbox in a Worker thread.

Usage: Metering

Metering allows you to set a limit on the amount of processing that the guest can do, which can be useful to catch infinite loops, especially if you don't trust the guest code. The sandbox.meter counter counts up as the guest executes instructions. If the limit is reached, the guest will halt and the sandbox will throw an exception.

import Sandbox from 'xs-sandbox';

const sandbox = await Sandbox.create({
  meteringLimit: 5000,
  meteringInterval: 1000,
});

try {
  sandbox.evaluate('while (true) {}') // Infinite loop
} catch (e) {
  console.log(e); // "metering limit reached"
  console.log(sandbox.meter);
}

Be careful with meter limits because the limit can be hit at any time and it halts the machine without processing any catch blocks in the guest code, which may leave the guest in an inconsistent state. It is strongly recommended not to use the sandbox again after it has hit a metering limit.

Promises and the event loop

The guest event loop is processed synchronously by evaluate and sendMessage, so there will never be pending jobs when sendMessage returns. Example:

import Sandbox from 'xs-sandbox';

const sandbox = await Sandbox.create();
sandbox.receiveMessage = console.log;

sandbox.evaluate(`
  async function myAsyncFunc() {
    sendMessage('before await');
    await Promise.resolve();
    sendMessage('after await');
  }

  sendMessage('before calling myAsyncFunc');
  myAsyncFunc();
  sendMessage('after calling myAsyncFunc');
`);
console.log('after evaluate');

This prints:

  1. before calling myAsyncFunc
  2. before await
  3. after calling myAsyncFunc
  4. after await
  5. after evaluate

Points 4 and 5 show that the promise job queue is processed before evaluate returns.

Debugging

There is no way to attach a debugger to the guest, but the following debug assistance has been provided:

  • console.log is provided in the guest and forwards its arguments to the host console via JSON serialization.
  • The stack of a thrown Error in the guest will be passed to the host. (But stacks from host errors are not visible to the guest for security reasons).

Guest Environment and Globals

The environment in which the guest script runs is a vanilla ECMAScript environment with no I/O APIs except sendMessage, receiveMessage, evaluate, and console.log. You can define your own APIs for the guest by first evaluating your own setup script which implements APIs in terms of sendMessage and receiveMessage.

Known issues

  • I've had issues under node.js 18 with the WASM crashing, but it seems to be working ok in the browser and in node.js 20 (in Windows).
  • When building without optimization, there's a weird behavior where the fxRunID C function (compiled to WASM) seems to cause a spike in memory usage by multiple GB for a few seconds, and if running in Node then the node process takes some time to exit at the end (tens of seconds). The released version on npm is built with optimization and so doesn't have this problem.

Maintainer notes

This depends on moddable as a git submodule. It uses a fork of the original moddable repo because there were a few changes I needed to make to get it to work. The biggest change is that I had to disable stack checks in various places because the stack depth calc didn't seem to work in WASM.

To clone this project, clone it with submodules:

git clone --recurse-submodules ...

Or if you've already cloned without submodules, run:

git submodule update --init --recursive

To build the project:

npm install
npm run build
npm test

License

The source code in this project is MIT licensed. The project incorporates a fork of Moddable's runtime engine as a git submodule, which is under the GNU Lesser General Public License v3 and Apache License Version 2.0. The fork only contains 4 line changes to get it to compile and disable stack checking which seems to be incompatible with the WASM runtime. Please refer to the Moddable SDK license information.

The distributed package on NPM incorporates all 3 licenses.

A copy of the licenses from Moddable SDK is distributed under ./moddable-sdk-licenses for reference. The MIT license is available at ./MIT-LICENSE.