isolated-function 0.1.0 → 0.1.2
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/LICENSE.md +0 -0
- package/README.md +8 -8
- package/package.json +14 -14
- package/src/compile.js +63 -4
package/LICENSE.md
CHANGED
|
File without changes
|
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ npm install isolated-function --save
|
|
|
41
41
|
|
|
42
42
|
## Quickstart
|
|
43
43
|
|
|
44
|
-
**isolated-
|
|
44
|
+
**isolated-function** is a modern solution for running untrusted code in Node.js.
|
|
45
45
|
|
|
46
46
|
```js
|
|
47
47
|
const isolatedFunction = require('isolated-function')
|
|
@@ -60,11 +60,9 @@ console.log({ value, profiling })
|
|
|
60
60
|
await teardown()
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
The hosted code runs in a separate process with limited permissions, returning the result and profiling the execution.
|
|
64
|
-
|
|
65
63
|
### Minimal privilege execution
|
|
66
64
|
|
|
67
|
-
The hosted code
|
|
65
|
+
The hosted code runs in a separate process, with minimal privilege, using [Node.js permission model API](https://nodejs.org/api/permissions.html#permission-model).
|
|
68
66
|
|
|
69
67
|
```js
|
|
70
68
|
const [fn, teardown] = isolatedFunction(() => {
|
|
@@ -76,7 +74,7 @@ await fn()
|
|
|
76
74
|
// => PermissionError: Access to 'FileSystemWrite' has been restricted.
|
|
77
75
|
```
|
|
78
76
|
|
|
79
|
-
Any of the following interaction will throw an error:
|
|
77
|
+
If you exceed your limit, an error will occur. Any of the following interaction will throw an error:
|
|
80
78
|
|
|
81
79
|
- Native modules
|
|
82
80
|
- Child process
|
|
@@ -92,7 +90,7 @@ The hosted code is parsed for detecting `require`/`import` calls and install the
|
|
|
92
90
|
```js
|
|
93
91
|
const [isEmoji, teardown] = isolatedFunction(emoji => {
|
|
94
92
|
/* this dependency only exists inside the isolated function */
|
|
95
|
-
const isEmoji = require('is-standard-emoji')
|
|
93
|
+
const isEmoji = require('is-standard-emoji@1.0.0') // default is latest
|
|
96
94
|
return isEmoji(emoji)
|
|
97
95
|
})
|
|
98
96
|
|
|
@@ -101,11 +99,11 @@ await isEmoji('foo') // => false
|
|
|
101
99
|
await teardown()
|
|
102
100
|
```
|
|
103
101
|
|
|
104
|
-
The dependencies, along with the hosted code, are bundled into a single file that will be evaluated at runtime.
|
|
102
|
+
The dependencies, along with the hosted code, are bundled by [esbuild](https://esbuild.github.io/) into a single file that will be evaluated at runtime.
|
|
105
103
|
|
|
106
104
|
### Execution profiling
|
|
107
105
|
|
|
108
|
-
Any hosted code execution
|
|
106
|
+
Any hosted code execution will be run in their own separate process:
|
|
109
107
|
|
|
110
108
|
```js
|
|
111
109
|
/** make a function to consume ~128MB */
|
|
@@ -130,6 +128,8 @@ console.log(profiling)
|
|
|
130
128
|
// }
|
|
131
129
|
```
|
|
132
130
|
|
|
131
|
+
Each execution has a profiling, which helps understand what happened.
|
|
132
|
+
|
|
133
133
|
### Resource limits
|
|
134
134
|
|
|
135
135
|
You can limit a **isolated-function** by memory:
|
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.1.
|
|
5
|
+
"version": "0.1.2",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./src/index.js"
|
|
@@ -62,6 +62,18 @@
|
|
|
62
62
|
"files": [
|
|
63
63
|
"src"
|
|
64
64
|
],
|
|
65
|
+
"scripts": {
|
|
66
|
+
"clean": "rm -rf node_modules",
|
|
67
|
+
"contributors": "(npx git-authors-cli && npx finepack && git add package.json && git commit -m 'build: contributors' --no-verify) || true",
|
|
68
|
+
"coverage": "c8 report --reporter=text-lcov > coverage/lcov.info",
|
|
69
|
+
"lint": "standard",
|
|
70
|
+
"postrelease": "npm run release:tags && npm run release:github && (ci-publish || npm publish --access=public)",
|
|
71
|
+
"pretest": "npm run lint",
|
|
72
|
+
"release": "standard-version -a",
|
|
73
|
+
"release:github": "github-generate-release",
|
|
74
|
+
"release:tags": "git push --follow-tags origin HEAD:master",
|
|
75
|
+
"test": "c8 ava"
|
|
76
|
+
},
|
|
65
77
|
"license": "MIT",
|
|
66
78
|
"commitlint": {
|
|
67
79
|
"extends": [
|
|
@@ -85,17 +97,5 @@
|
|
|
85
97
|
"simple-git-hooks": {
|
|
86
98
|
"commit-msg": "npx commitlint --edit",
|
|
87
99
|
"pre-commit": "npx nano-staged"
|
|
88
|
-
},
|
|
89
|
-
"scripts": {
|
|
90
|
-
"clean": "rm -rf node_modules",
|
|
91
|
-
"contributors": "(npx git-authors-cli && npx finepack && git add package.json && git commit -m 'build: contributors' --no-verify) || true",
|
|
92
|
-
"coverage": "c8 report --reporter=text-lcov > coverage/lcov.info",
|
|
93
|
-
"lint": "standard",
|
|
94
|
-
"postrelease": "npm run release:tags && npm run release:github && (ci-publish || npm publish --access=public)",
|
|
95
|
-
"pretest": "npm run lint",
|
|
96
|
-
"release": "standard-version -a",
|
|
97
|
-
"release:github": "github-generate-release",
|
|
98
|
-
"release:tags": "git push --follow-tags origin HEAD:master",
|
|
99
|
-
"test": "c8 ava"
|
|
100
100
|
}
|
|
101
|
-
}
|
|
101
|
+
}
|
package/src/compile.js
CHANGED
|
@@ -37,17 +37,74 @@ const detectDependencies = code => {
|
|
|
37
37
|
node.arguments.length === 1 &&
|
|
38
38
|
node.arguments[0].type === 'Literal'
|
|
39
39
|
) {
|
|
40
|
-
|
|
40
|
+
const dependency = node.arguments[0].value
|
|
41
|
+
// Check if the dependency string contains '@' symbol for versioning
|
|
42
|
+
if (dependency.includes('@')) {
|
|
43
|
+
// Split by '@' to separate module name and version
|
|
44
|
+
const parts = dependency.split('@')
|
|
45
|
+
// Handle edge case where module name might also contain '@' (scoped packages)
|
|
46
|
+
const moduleName = parts.length > 2 ? `${parts[0]}@${parts[1]}` : parts[0]
|
|
47
|
+
const version = parts[parts.length - 1]
|
|
48
|
+
dependencies.add(`${moduleName}@${version}`)
|
|
49
|
+
} else {
|
|
50
|
+
dependencies.add(`${dependency}@latest`)
|
|
51
|
+
}
|
|
41
52
|
}
|
|
42
53
|
},
|
|
43
54
|
ImportDeclaration (node) {
|
|
44
|
-
|
|
55
|
+
const source = node.source.value
|
|
56
|
+
if (source.includes('@')) {
|
|
57
|
+
const parts = source.split('@')
|
|
58
|
+
const moduleName = parts.length > 2 ? `${parts[0]}@${parts[1]}` : parts[0]
|
|
59
|
+
const version = parts[parts.length - 1]
|
|
60
|
+
dependencies.add(`${moduleName}@${version}`)
|
|
61
|
+
} else {
|
|
62
|
+
dependencies.add(`${source}@latest`)
|
|
63
|
+
}
|
|
45
64
|
}
|
|
46
65
|
})
|
|
47
66
|
|
|
48
67
|
return Array.from(dependencies)
|
|
49
68
|
}
|
|
50
69
|
|
|
70
|
+
const transformDependencies = code => {
|
|
71
|
+
const ast = acorn.parse(code, { ecmaVersion: 2020, sourceType: 'module' })
|
|
72
|
+
|
|
73
|
+
let newCode = ''
|
|
74
|
+
let lastIndex = 0
|
|
75
|
+
|
|
76
|
+
// Helper function to process and transform nodes
|
|
77
|
+
const processNode = node => {
|
|
78
|
+
if (node.type === 'Literal' && node.value.includes('@')) {
|
|
79
|
+
// Extract module name without version
|
|
80
|
+
const [moduleName] = node.value.split('@')
|
|
81
|
+
// Append code before this node
|
|
82
|
+
newCode += code.substring(lastIndex, node.start)
|
|
83
|
+
// Append transformed dependency
|
|
84
|
+
newCode += `'${moduleName}'`
|
|
85
|
+
// Update lastIndex to end of current node
|
|
86
|
+
lastIndex = node.end
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Traverse the AST to find require and import declarations
|
|
91
|
+
walk.simple(ast, {
|
|
92
|
+
CallExpression (node) {
|
|
93
|
+
if (node.callee.name === 'require' && node.arguments.length === 1) {
|
|
94
|
+
processNode(node.arguments[0])
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
ImportDeclaration (node) {
|
|
98
|
+
processNode(node.source)
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
// Append remaining code after last modified dependency
|
|
103
|
+
newCode += code.substring(lastIndex)
|
|
104
|
+
|
|
105
|
+
return newCode
|
|
106
|
+
}
|
|
107
|
+
|
|
51
108
|
const getTmp = async content => {
|
|
52
109
|
const cwd = await fs.mkdtemp(path.join(tmpdir(), 'compile-'))
|
|
53
110
|
await fs.mkdir(cwd, { recursive: true })
|
|
@@ -60,8 +117,10 @@ const getTmp = async content => {
|
|
|
60
117
|
}
|
|
61
118
|
|
|
62
119
|
module.exports = async snippet => {
|
|
63
|
-
const
|
|
64
|
-
const dependencies = detectDependencies(
|
|
120
|
+
const compiledTemplate = generateTemplate(snippet)
|
|
121
|
+
const dependencies = detectDependencies(compiledTemplate)
|
|
122
|
+
const tmp = await getTmp(transformDependencies(compiledTemplate))
|
|
123
|
+
|
|
65
124
|
await $(packageManager.init, { cwd: tmp.cwd })
|
|
66
125
|
await $(`${packageManager.install} ${dependencies.join(' ')}`, {
|
|
67
126
|
cwd: tmp.cwd
|