Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/astrohelm/isolation
How often do you see libraries which mutates global variables. Or how often do you check libraries actions 🥸 ?This library prevents unexpected behavior and global pollution.
https://github.com/astrohelm/isolation
astrohelm container context dependency-injection isolation javascript loader nodejs pollution-control realm realms runner sandbox script script-loader security v8 vm wrapper zero-dependencies
Last synced: 3 months ago
JSON representation
How often do you see libraries which mutates global variables. Or how often do you check libraries actions 🥸 ?This library prevents unexpected behavior and global pollution.
- Host: GitHub
- URL: https://github.com/astrohelm/isolation
- Owner: astrohelm
- License: mit
- Created: 2023-08-25T21:04:33.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2024-08-24T15:02:51.000Z (6 months ago)
- Last Synced: 2024-09-29T21:39:31.577Z (4 months ago)
- Topics: astrohelm, container, context, dependency-injection, isolation, javascript, loader, nodejs, pollution-control, realm, realms, runner, sandbox, script, script-loader, security, v8, vm, wrapper, zero-dependencies
- Language: JavaScript
- Homepage: https://astrohelm.ru
- Size: 257 KB
- Stars: 3
- Watchers: 2
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
- Authors: AUTHORS
Awesome Lists containing this project
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**
```bash
npm i isolation --save
```
**Basic Usage**
- **Prevent intentionally damage**
It will stop tricky users code, while you don't allow it.
```javascript
// unchecked-dangerous-library index.js
const fs = require('fs');
fs.rm(process.cwd(), { recursive: true }); // Ha ha, no-code developer
``````javascript
// routes/route/get.js
const dangerousLibrary = require('unchecked-dangerous-library'); // Point where dangerous library initialized
// ... other logic
``````javascript
// 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.
```javascript
// 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
``````javascript
// routes/route/get.js
const dangerousLibrary = require('unchecked-dangerous-library'); // Point where dangerous library initialized
// ... other logic
``````javascript
// 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.```javascript
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 seemodule
orexports
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;```javascript
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**
```javascript
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:
```js
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**
- Optiondepth:number|boolean
nested directories restrictions, **default true**
- Optionflat:boolean
result with nested scripts will be flat, **default false**-
read
Allow you to read source codes from files and directories```javascript
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:
```js
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```javascript
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```javascript
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.```js
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;
```js
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](#module-types) |
| **ctx** | object | - | Realm context, see [Context API](#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](./types/options.d.ts#L51) | - | Isolation restrictions, see [Access API](#reader-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](./types/options.d.ts#L63) | - | Configuration for VM.Script initialization |
| **runOpts** | [RunningCodeOptions](./types/options.d.ts#L62) | { timeout: 1000 } | Configuration for VM.Script execution |Copyright & contributors
Copyright © 2023 Astrohelm contributors.
This library is MIT licensed license.
And it is part of Astrohelm solutions.