0.0.3 • Published 10 months ago

isolated-function v0.0.3

Weekly downloads
-
License
MIT
Repository
github
Last release
10 months ago

Install

npm install isolated-function --save

Quickstart

isolated-function is a modern solution for running untrusted code in Node.js.

const isolatedFunction = require('isolated-function')

/* create an isolated-function, with resources limitation */
const [sum, teardown] = isolatedFunction((y, z) => y + z, {
  memory: 128, // in MB
  timeout: 10000 // in milliseconds
})

/* interact with the isolated-function */
const { value, profiling } = await sum(3, 2)

/* close resources associated with the isolated-function initialization */
await teardown()

Minimal privilege execution

The hosted code runs in a separate process, with minimal privilege, using Node.js permission model API.

const [fn, teardown] = isolatedFunction(() => {
  const fs = require('fs')
  fs.writeFileSync('/etc/passwd', 'foo')
})

await fn()
// => PermissionError: Access to 'FileSystemWrite' has been restricted.

If you exceed your limit, an error will occur. Any of the following interaction will throw an error:

  • Native modules
  • Child process
  • Worker Threads
  • Inspector protocol
  • File system access
  • WASI

Auto install dependencies

The hosted code is parsed for detecting require/import calls and install these dependencies:

const [isEmoji, teardown] = isolatedFunction(input => {
  /* this dependency only exists inside the isolated function */
  const isEmoji = require('is-standard-emoji@1.0.0') // default is latest
  return isEmoji(input)
})

await isEmoji('🙌') // => true
await isEmoji('foo') // => false
await teardown()

The dependencies, along with the hosted code, are bundled by esbuild into a single file that will be evaluated at runtime.

Execution profiling

Any hosted code execution will be run in their own separate process:

/** make a function to consume ~128MB */
const [fn, teardown] = isolatedFunction(() => {
  const storage = []
  const oneMegabyte = 1024 * 1024
  while (storage.length < 78) {
    const array = new Uint8Array(oneMegabyte)
    for (let ii = 0; ii < oneMegabyte; ii += 4096) {
      array[ii] = 1
    }
    storage.push(array)
  }
})
t.teardown(cleanup)

const { value, profiling } = await fn()
console.log(profiling)
// {
//   memory: 128204800,
//   duration: 54.98325
// }

Each execution has a profiling, which helps understand what happened.

Resource limits

You can limit a isolated-function by memory:

const [fn, teardown] = isolatedFunction(() => {
  const storage = []
  const oneMegabyte = 1024 * 1024
  while (storage.length < 78) {
    const array = new Uint8Array(oneMegabyte)
    for (let ii = 0; ii < oneMegabyte; ii += 4096) {
      array[ii] = 1
    }
    storage.push(array)
  }
}, { memory: 64 })

await fn()
// =>  MemoryError: Out of memory

or by execution duration:

const [fn, teardown] = isolatedFunction(() => {
  const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
  await delay(duration)
  return 'done'
}, { timeout: 50 })

await fn(100)
// =>  TimeoutError: Execution timed out

Logging

The logs are collected into a logging object returned after the execution:

const [fn, teardown] = isolatedFunction(() => {
  console.log('console.log')
  console.info('console.info')
  console.debug('console.debug')
  console.warn('console.warn')
  console.error('console.error')
  return 'done'
})

const { logging } await fn()

console.log(logging)
// {
//   log: ['console.log'],
//   info: ['console.info'],
//   debug: ['console.debug'],
//   warn: ['console.warn'],
//   error: ['console.error']
// }

Error handling

Any error during isolated-function execution will be propagated:

const [fn, cleanup] = isolatedFunction(() => {
  throw new TypeError('oh no!')
})

const result = await fn()
// TypeError: oh no!

You can also return the error instead of throwing it with { throwError: false }:

const [fn, cleanup] = isolatedFunction(() => {
  throw new TypeError('oh no!')
})

const { isFullfiled, value } = await fn()

if (!isFufilled) {
  console.error(value)
  // TypeError: oh no!
}

API

isolatedFunction(code, options)

code

Required Type: function

The hosted function to run.

options

memory

Type: number Default: Infinity

Set the function memory limit, in megabytes.

throwError

Type: boolean Default: false

When is true, it returns the error rather than throw it.

The error will be accessible against { value: error, isFufilled: false } object.

Set the function memory limit, in megabytes.

timeout

Type: number Default: Infinity

Timeout after a specified amount of time, in milliseconds.

tmpdir

Type: function

It setup the temporal folder to be used for installing code dependencies.

The default implementation is:

const tmpdir = async () => {
  const cwd = await fs.mkdtemp(path.join(require('os').tmpdir(), 'compile-'))
  await fs.mkdir(cwd, { recursive: true })
  const cleanup = () => fs.rm(cwd, { recursive: true, force: true })
  return { cwd, cleanup }
}

=> (fn(...args), teardown())

fn

Type: function

The isolated function to execute. You can pass arguments over it.

teardown

Type: function

A function to be called to release resources associated with the isolated-function.

Environment Variables

ISOLATED_FUNCTIONS_MINIFY

Default: true

When is false, it disabled minify the compiled code.

DEBUG

Pass DEBUG=isolated-function for enabling debug timing output.

License

isolated-function © Kiko Beats, released under the MIT License. Authored and maintained by Kiko Beats with help from contributors.

kikobeats.com · GitHub @Kiko Beats · X @Kikobeats

0.1.27

4 months ago

0.1.28

4 months ago

0.1.26

8 months ago

0.1.25

9 months ago

0.1.24

9 months ago

0.1.23

10 months ago

0.1.22

10 months ago

0.1.21

10 months ago

0.1.20

10 months ago

0.1.19

10 months ago

0.1.18

10 months ago

0.1.17

10 months ago

0.1.16

10 months ago

0.1.15

10 months ago

0.1.14

10 months ago

0.1.13

10 months ago

0.1.12

10 months ago

0.1.11

10 months ago

0.1.10

10 months ago

0.1.9

10 months ago

0.1.8

10 months ago

0.1.7

10 months ago

0.1.6

10 months ago

0.1.5

10 months ago

0.1.4

10 months ago

0.1.3

10 months ago

0.1.2

10 months ago

0.1.1

10 months ago

0.1.0

10 months ago

0.0.6

10 months ago

0.0.5

10 months ago

0.0.4

10 months ago

0.0.3

10 months ago