isolated-function
v0.1.26
Published
Runs untrusted code in a Node.js v8 sandbox.
Downloads
168
Maintainers
Readme
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('[email protected]') // 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