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

isolation

v3.1.0

Published

How often do you see libraries which mutates global variables Or how often do you check libraries actions ? This library provides script isolation in custom contexts to solve this kind of issues.

Downloads

27

Readme

Isolation

Why should i use it ? How often do you see libraries which mutates global variables Or how often do you check libraries actions ? Library provides script isolation in custom contexts to solve this kind of issues. Also, isolation prevents global scope and prototypes pollution.

[!TIP]

Possible use case

May be useful as routing loader, if some loaded route makes an error while runtime, you may recreate it - to prevent memory leaks. Another worlds, with this library you can create multi-tenant applications;

Installation

npm i isolation --save

Basic Usage

  • Prevent intentionally damage

    It will stop tricky users code, while you don't allow it.

    // unchecked-dangerous-library index.js
    const fs = require('fs');
    fs.rm(process.cwd(), { recursive: true }); // Ha ha, no-code developer
    // routes/route/get.js
    const dangerousLibrary = require('unchecked-dangerous-library'); // Point where dangerous library initialized
    // ... other logic
    // index.js
    const Isolation = require('isolation');
    const routes = Isolation.read('./routes');
    // ⚠️ Will throw an error because fs doesn't allowed
  • Prevent unintentionally damage

    This solves problem where libraries used to mutate global variables.

    // unchecked-dangerous-library index.js
    var console = msg => process.stdout.write(msg); // Someone just want different implementation for console
    global.console = console;
    console('Here it works fine');
    String.prototype = {}; // Or just mutating prototypes
    // routes/route/get.js
    const dangerousLibrary = require('unchecked-dangerous-library'); // Point where dangerous library initialized
    // ... other logic
    // index.js
    const Isolation = require('isolation');
    Isolation.read('./routes');
    console.log('All works fine');
    console('Here it not works'); // Will throw an error
    String.prototype; // Will be as default

Module types / Script syntax

[!CAUTION]

You can run any script from string, just like eval, but in custom VM container. But you shouldn't use it for unknown script evaluation, it may create security issues.

Commonjs

By default Isolation will use Standard Nodejs syntax. With this type of syntax it will provide to your realms global variables such as:

  • require function, which is almost same as nodejs require, but with extra cup of isolation;
  • module & exports variables provided to manipulate with exports between modules;
  • __filename & __dirname are same as with default nodejs realm;

[!IMPORTANT]

You always should use module.exports to export, otherwise you will see undefined as the result of realm execution.

const Isolation = require('isolation');
console.log(new Isolation(`module.exports = { field: 'value' };`).execute()); // Output: { field: 'value' }
console.log(Isolation.execute(`module.exports = (a, b) => a + b;`)(2 + 2)); // Output: 4
console.log(Isolation.execute(`(a, b) => a + b;`)(2 + 2)); // Output: undefined
Isolation.execute(`module.exports = async (a, b) => a + b;`)(2 + 2).then(console.log); // Output: 4

ISO

This type of syntax will stand your script alone without any of extra global variables. That means that you would not see module or exports in your environment. But your context variables will still work well.

[!IMPORTANT]

In this mode, your realms will export result of the last expression, that means that you should put a reference to your variable or expression at the end of the file / row;

const Isolation = require('isolation');
const options = { type: 'iso' };
console.log(new Isolation(`{ field: 'value' };`, options).execute()); // Output: { field: 'value' }
console.log(Isolation.execute(`(a, b) => a + b;`, options)(2 + 2)); // Output: 4
Isolation.execute(`async (a, b) => a + b;`, options)(2 + 2).then(console.log); // Output: 4

ESM

Isolation doesn't yet support esm syntax. That's because currently node.vm ESM modules are experimental.

Context API

You can create custom context or use default presets with context api. This will allow you to provide your custom variables to the context without requiring any module.

[!TIP]

Remember to reuse your contexts. This will increase performance of your application. To help you with this we have default contexts:

Context example

const { contextify, execute } = require('isolation');
const custom = contextify({ console }); // ⚠️ Object will be mutated
execute(`console.log(123);`, { ctx: custom }); // STD Output: 123
execute(`console.log(123);`); // No STD output, because different stdout stream

Also its allow you to change program behavior with something like:

const ctx = Isolation.contextify({ a: 1000, b: 10 });
const realm = new Isolation(`module.exports = a - b`, { ctx });
realm.execute(); // Output: 990
realm.execute({ ...ctx, a: 0 }); // Output: -10
realm.execute({ ...ctx, b: 7 }); // Output: 993

Default contexts

Default contexts are accessible from Isolation.contextify, there you can find:

  • Isolation.contextify.EMPTY, that just empty context
  • Isolation.contextify.COMMON, timers, buffer, fetch etc...
  • Isolation.contextify.NODE, global, console, process & COMMON context You should'nt use NODE context, it may create security issues, otherwise it may road to possible context escaping.

Reader API

Reader allow you to run scripts from files and extends possible provided options with:

  • Option prepare:boolean reader will return non-executed scripts, default false

  • Option depth:number|boolean nested directories restrictions, default true

  • Option flat:boolean result with nested scripts will be flat, default false

  • read Allow you to read source codes from files and directories

    const Isolation = require('isolation');
    Isolation.read('./path/to/script.js').then(console.log); // Output: result of script execution
    Isolation.read('./path/to').then(console.log); // Output: { script: any }
    Isolation.read('./path/to', { prepare: true }).then(console.log); // Output: { script: Script {} }

    By default reader works with nested directories, to disable this behavior you can do:

    const Isolation = require('isolation');
    Isolation.read('./path/to', { depth: false });
    // Or limit it:
    Isolation.read('./path/to', { depth: 3 });
  • read.file Allow you to execute script from single file

    const Isolation = require('isolation');
    Isolation.read.file('./path/to/script.js').then(console.log); // Output: result of script execution
    Isolation.read.file('./path/to/script.js', { prepare: true }).then(console.log); // Output: Script {}
  • read.dir Allow you to execute multiple scripts from directory

    const Isolation = require('isolation');
    Isolation.read.dir('./path/to').then(console.log); // Output: { script: any, deep: { script: any } }
    Isolation.read.dir('./path/to', { prepare: true }).then(console.log); Output: { script: Script {} }
    Isolation.read.dir('./path/to', { depth: false }).then(console.log); // Output: { script: any }

Access control

You may control access over realm submodules and reader api;

[!NOTE]

If access doesn't provided realm submodules would'nt be accessible and reader will read all files in directed repository.

const options = { access: pathOrModule => pathOrModule === 'fs' || pathOrModule.endsWith('.js') };
Isolation.execute('module.exports = require("fs")', options);
Isolation.read('./path/to/script.js', options);
// Or
const options2 = {
  access: {
    reader: path => true, // Directory reader
    realm: module => {}, //  Realm submodules
  },
};

Library substitution

You can replace result of require for specific libraries with anything what you want;

const Isolation = require('isolation');
const src = `
  const fs = require('fs');
  module.exports = fs.readFile('Isolation.js');
`;

const sub = name => {
  if (name !== 'fs') return true;
  return {
    readFile: filename => filename + ' Works !',
  };
};

const result = Isolation.execute(src, { access: { realm: sub } });
console.log(result); // Output: Isolation.js Works !

Possible script options

| Option | Possible | Default | Description | | ---------------- | ---------------------------------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------- | | type | iso | cjs | cjs | Type of script handling, see syntax types | | ctx | object | - | Realm context, see Context API | | filename | string | ISO | Name of the module , also it is global variable __filename for root realm | | dir | string | process.cwd() | Module directory , also it is global variable __dirname and realm require start point | | npmIsolation | boolean | false | Controls npm modules isolation | | access | Access | - | Isolation restrictions, see Access API | | prepare | boolean | false | Reader would'nt execute script for you | | flat | boolean | false | Reader will flat nested scripts | | depth | boolean | number | true | Restricts dir reading depth | | realmOpts | ScriptOptions | - | Configuration for VM.Script initialization | | runOpts | RunningCodeOptions | { timeout: 1000 } | Configuration for VM.Script execution |