isolated-function 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -13
- package/package.json +2 -1
- package/src/compile.js +3 -3
- package/src/index.js +50 -10
- package/src/template/index.js +15 -11
package/README.md
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
<h3 align="center">
|
|
2
|
+
<img src="https://github.com/Kikobeats/isolated-function/blob/master/logo.png?raw=true" width="200">
|
|
3
|
+
<br>
|
|
4
|
+
<p>isolated-functions</p>
|
|
5
|
+
<a target="_blank" rel="noopener noreferrer nofollow"><img src="https://img.shields.io/github/tag/Kikobeats/isolated-function.svg?style=flat-square" style="max-width: 100%;"></a>
|
|
6
|
+
<a href="https://coveralls.io/github/Kikobeats/isolated-function" rel="nofollow"><img src="https://img.shields.io/coveralls/Kikobeats/isolated-function.svg?style=flat-square" alt="Coverage Status" style="max-width: 100%;"></a>
|
|
7
|
+
<a href="https://www.npmjs.org/package/isolated-function" rel="nofollow"><img src="https://img.shields.io/npm/dm/isolated-function.svg?style=flat-square" alt="NPM Status" style="max-width: 100%;"></a>
|
|
8
|
+
</h3>
|
|
9
|
+
|
|
10
|
+
## Highlights
|
|
11
|
+
|
|
12
|
+
- Based in [Node.js Permission Model](https://nodejs.org/api/permissions.html#permission-model)
|
|
10
13
|
- Auto install npm dependencies.
|
|
14
|
+
- Memory limit support.
|
|
11
15
|
- Timeout support.
|
|
12
16
|
|
|
13
17
|
## Install
|
|
@@ -19,10 +23,10 @@ npm install isolated-function --save
|
|
|
19
23
|
## Usage
|
|
20
24
|
|
|
21
25
|
```js
|
|
22
|
-
const
|
|
26
|
+
const isolatedFunction = require('isolated-function')
|
|
23
27
|
|
|
24
28
|
/* This function will run in a sandbox, in a separate process */
|
|
25
|
-
const sum =
|
|
29
|
+
const sum = isolatedFunction((y, z) => y + z)
|
|
26
30
|
|
|
27
31
|
/* Interact with it as usual from your main code */
|
|
28
32
|
const result = await sum(3, 2)
|
|
@@ -33,7 +37,7 @@ console.log(result)
|
|
|
33
37
|
You can also use `require' for external dependencies:
|
|
34
38
|
|
|
35
39
|
```js
|
|
36
|
-
const isEmoji =
|
|
40
|
+
const isEmoji = isolatedFunction(emoji => {
|
|
37
41
|
const isEmoji = require('is-standard-emoji')
|
|
38
42
|
return isEmoji(emoji)
|
|
39
43
|
})
|
|
@@ -48,7 +52,7 @@ It's intentionally not possible to expose any Node.js objects or functions direc
|
|
|
48
52
|
|
|
49
53
|
## API
|
|
50
54
|
|
|
51
|
-
###
|
|
55
|
+
### isolatedFunction(snippet, [options])
|
|
52
56
|
|
|
53
57
|
#### snippet
|
|
54
58
|
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "isolated-function",
|
|
3
3
|
"description": "Runs untrusted code in a Node.js v8 sandbox.",
|
|
4
4
|
"homepage": "https://github.com/Kikobeats/isolated-function",
|
|
5
|
-
"version": "0.0.
|
|
5
|
+
"version": "0.0.6",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./src/index.js"
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"v8"
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
|
+
"@kikobeats/time-span": "~1.0.5",
|
|
36
37
|
"acorn": "~8.12.1",
|
|
37
38
|
"acorn-walk": "~8.3.3",
|
|
38
39
|
"ensure-error": "~3.0.1",
|
package/src/compile.js
CHANGED
|
@@ -11,6 +11,8 @@ const path = require('path')
|
|
|
11
11
|
|
|
12
12
|
const generateTemplate = require('./template')
|
|
13
13
|
|
|
14
|
+
const MINIFY = process.env.ISOLATED_FUNCTIONS_MINIFY !== 'false'
|
|
15
|
+
|
|
14
16
|
const packageManager = (() => {
|
|
15
17
|
try {
|
|
16
18
|
execSync('which pnpm').toString().trim()
|
|
@@ -65,12 +67,10 @@ module.exports = async snippet => {
|
|
|
65
67
|
cwd: tmp.cwd
|
|
66
68
|
})
|
|
67
69
|
|
|
68
|
-
// return tmp
|
|
69
|
-
|
|
70
70
|
const result = await esbuild.build({
|
|
71
71
|
entryPoints: [tmp.filepath],
|
|
72
72
|
bundle: true,
|
|
73
|
-
|
|
73
|
+
minify: MINIFY,
|
|
74
74
|
write: false,
|
|
75
75
|
platform: 'node'
|
|
76
76
|
})
|
package/src/index.js
CHANGED
|
@@ -1,33 +1,73 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { deserializeError } = require('serialize-error')
|
|
4
|
+
const timeSpan = require('@kikobeats/time-span')()
|
|
4
5
|
const $ = require('tinyspawn')
|
|
6
|
+
const path = require('path')
|
|
5
7
|
|
|
6
8
|
const compile = require('./compile')
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
const createError = ({ name, message, ...props }) => {
|
|
11
|
+
const error = new Error(message)
|
|
12
|
+
error.name = name
|
|
13
|
+
Object.assign(error, props)
|
|
14
|
+
return error
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const flags = ({ filename, memory }) => {
|
|
18
|
+
const flags = ['--experimental-permission', `--allow-fs-read=${filename}`]
|
|
19
|
+
if (memory) flags.push(`--max-old-space-size=${memory}`)
|
|
20
|
+
return flags.join(' ')
|
|
13
21
|
}
|
|
14
22
|
|
|
15
|
-
module.exports = (snippet, { timeout = 0 } = {}) => {
|
|
23
|
+
module.exports = (snippet, { timeout = 0, memory } = {}) => {
|
|
16
24
|
if (typeof snippet !== 'function') throw new TypeError('Expected a function')
|
|
17
25
|
const compilePromise = compile(snippet)
|
|
18
26
|
|
|
19
27
|
const fn = async (...args) => {
|
|
28
|
+
let duration
|
|
20
29
|
try {
|
|
21
30
|
const { filepath } = await compilePromise
|
|
22
|
-
|
|
31
|
+
|
|
32
|
+
const cwd = path.dirname(filepath)
|
|
33
|
+
const filename = path.basename(filepath)
|
|
34
|
+
const cmd = `node ${flags({ filename, memory })} ${filename} ${JSON.stringify(args)}`
|
|
35
|
+
|
|
36
|
+
duration = timeSpan()
|
|
37
|
+
const { stdout } = await $(cmd, {
|
|
38
|
+
cwd,
|
|
23
39
|
timeout,
|
|
24
40
|
killSignal: 'SIGKILL'
|
|
25
41
|
})
|
|
26
|
-
const { isFulfilled, value } = JSON.parse(stdout)
|
|
27
|
-
|
|
42
|
+
const { isFulfilled, value, profiling } = JSON.parse(stdout)
|
|
43
|
+
profiling.duration = duration()
|
|
44
|
+
if (isFulfilled) return [value, profiling]
|
|
28
45
|
throw deserializeError(value)
|
|
29
46
|
} catch (error) {
|
|
30
|
-
if (error.
|
|
47
|
+
if (error.signalCode === 'SIGTRAP') {
|
|
48
|
+
throw createError({
|
|
49
|
+
name: 'MemoryError',
|
|
50
|
+
message: 'Out of memory',
|
|
51
|
+
profiling: { duration: duration() }
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (error.signalCode === 'SIGKILL') {
|
|
56
|
+
throw createError({
|
|
57
|
+
name: 'TimeoutError',
|
|
58
|
+
message: 'Execution timed out',
|
|
59
|
+
profiling: { duration: duration() }
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (error.code === 'ERR_ACCESS_DENIED') {
|
|
64
|
+
throw createError({
|
|
65
|
+
name: 'PermissionError',
|
|
66
|
+
message: `Access to '${error.permission}' has been restricted`,
|
|
67
|
+
profiling: { duration: duration() }
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
31
71
|
throw error
|
|
32
72
|
}
|
|
33
73
|
}
|
package/src/template/index.js
CHANGED
|
@@ -2,22 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
const SERIALIZE_ERROR = require('./serialize-error')
|
|
4
4
|
|
|
5
|
-
const generateTemplate = snippet =>
|
|
6
|
-
const template = `
|
|
5
|
+
const generateTemplate = snippet => `
|
|
7
6
|
const args = JSON.parse(process.argv[2])
|
|
8
7
|
|
|
9
8
|
;(async () => {
|
|
9
|
+
let value
|
|
10
|
+
let isFulfilled
|
|
11
|
+
|
|
10
12
|
try {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
value = await (${snippet.toString()})(...args)
|
|
14
|
+
isFulfilled = true
|
|
13
15
|
} catch (error) {
|
|
14
|
-
|
|
16
|
+
value = ${SERIALIZE_ERROR}(error)
|
|
17
|
+
isFulfilled = false
|
|
18
|
+
} finally {
|
|
19
|
+
console.log(JSON.stringify({
|
|
20
|
+
isFulfilled,
|
|
21
|
+
value,
|
|
22
|
+
profiling: { memory: process.memoryUsage().rss }
|
|
23
|
+
}))
|
|
15
24
|
}
|
|
16
|
-
})()
|
|
17
|
-
|
|
18
|
-
`
|
|
19
|
-
|
|
20
|
-
return template
|
|
21
|
-
}
|
|
25
|
+
})()`
|
|
22
26
|
|
|
23
27
|
module.exports = generateTemplate
|