isolated-function v0.0.3
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
4 months ago
4 months ago
8 months ago
9 months ago
9 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago