Advanced Concepts

This page contains advanced features and isn't necessary for integrating Intrinsic into an application. Intrinsic is built in such a way that you shouldn't need to make any code changes for it to run. Some of the features on this page can be used to further increase an application's performance. Other sections explain the decisions we make when building Intrinsic and how such decisions may affect an application protected by Intrinsic.

Native Modules

Native modules, unlike pure JavaScript modules, are written in C++ and have access to deeper V8 APIs and the capability to circumvent all the protections which Intrinsic has in place. This means that by default we do not allow native modules to be loaded as any vulnerabilities in the code will render the entire application insecure.

However, if you need a particular native module for your application, you can contact us and request that we virtualize the module. This will require us to hand-audit the code.

Virtualization

Virtualization is a manual process where Intrinsic examines the source code of an npm module, determines the inputs and outputs, safely wraps the module, and usually creates a system for defining policies for the module. This is commonly done to wrap modules with either a native module component, or one that does I/O in some manner.

Some example modules which Intrinsic has virtualized includes the PostgreSQL module pg, the gRPC module grpc, and fibers. The pg module deals with a database. It is feasible that multiple sandboxes would need to talk to the same database but perhaps perform different actions. By virtualizing the library we are able to create a policy system for fine-grained access, such as exactly which queries can be executed. The grpc module deals with communications and we offer policies for specifying which commands can be sent to which servers. Finally, the fibers module doesn't deal with I/O, but it is a native module. Since we've vetted the module we know it's innocuous and we wrap it safely.

The sign that a library needs to be virtualized is that the following violation will be logged:

INTRINSIC WARNING: /path/to/module/node-v48-linux-x64-glibc/xyz.node
  is a native addon. Contact Intrinsic if you want to use it in enforcement mode.

Once a library is virtualized it's typically enabled by running the .virtualizeLib(moduleName) command in your Intrinsic entry point file.

Note: you can't simply call .virtualizeLib() and specify the name of any native module triggering the above error. It will only work with the native modules for which Intrinsic has specifically virtualized.

Multiprocess Mode

Typically Intrinsic adds a small overhead to the performance of applications. Since we interact at the layer where I/O is performed the overhead is usually unnoticed. CPU heavy work usually suffers from no performance loss. However, if you do find that your application is performing noticeably slower when run with Intrinsic, you have a couple options.

First, reach out to us and let us know what the bottleneck is. We can run benchmarks with your code and fix any performance issues you may find. Second, you can attempt to enable multiprocess mode. In this mode Intrinsic will automatically split sandboxes across different process instances. This is really only useful for Intrinsic for Node.js, as Lambda uses a single sandbox.

To enable multiprocess mode, add a call to .multiprocess() when configuring Intrinsic. .multiprocess() also accepts an optional object with a maxProcs field set to the number of processes you would like to run:

const intrinsic = require('@intrinsic/intrinsic');
const cpus = require('os').cpus().length;

intrinsic(__filename)
  .multiprocess({ maxProcs: cpus })
  .loadPolicies('./intrinsic-policy.js')
  .run('./app.js');

Using the above example, if your application has 10 sandboxes spread across 4 processes, each process will have between 2 and 3 sandboxes within it.

Note: by default, .multiprocess will set maxProcs to require('os').cpus().length - 1.

Side-Channel Communication

Each sandbox has its own set of policies applied to it. Keeping data in one sandbox and out of another sandbox is an elementary component of how Intrinsic keeps Node.js applications safe. This often means that if communication between two sandboxes can happen then the protections of Intrinsic will start to break down. Because of this we need to make sure that inter-sandbox communications, i.e. side-channels, are not an effective tool for passing messages.

There are many such side-channels in Node.js by default. For example, if we were to allow reading and writing to a global variable which was accessible from two different sandboxes then there would be an effective side-channel. Luckily we've prevented that from happening; each sandbox gets their own globals.

Another communication channel would be consuming/reading numeric counters. For example, incrementing file descriptors. As an example of this one sandbox could consume lots of file descriptors, say ranges of 100 or 200 at a time, and do so every 100ms. Another function could then poll for the next available file descriptor every 100ms. Depending on whether ~100 or ~200 have been used then the function would interpret that as a 1 or a 0. Thus, we've created a very low bandwidth side-channel attack. Don't worry, we've got that one covered as well.

The most obvious, and potentially highest-bandwidth communication side-channel can happen by way of a stateful service—like when one sandbox can write data to a database and another can read from it. This is why we recommend virtualizing database modules instead of allowing applications to connect to a database directly using a TCP connection (which is the fallback behavior when a database library isn't virtualized).

Our product is one created out of paranoia for things like this. This does, however, mean that we have to write a lot of interesting code and change subtle Node.js behaviors in typically innocent ways. Sometimes libraries will depend on these subtle, often undocumented, features. It is for reasons like these that Intrinsic will work closely with you to test support of your application, and the third party modules it depends on.