isolated-function 0.1.47 → 0.1.49
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 +2 -2
- package/package.json +4 -3
- package/src/compile/index.js +0 -1
- package/src/compile/install-dependencies.js +10 -11
- package/src/errors.js +32 -0
package/README.md
CHANGED
|
@@ -155,7 +155,7 @@ await fn('🙌') // => true
|
|
|
155
155
|
await teardown()
|
|
156
156
|
```
|
|
157
157
|
|
|
158
|
-
If the code tries to require a package not in the allowed list,
|
|
158
|
+
If the code tries to require a package not in the allowed list, a `DependencyUnallowedError` is thrown **before** any npm install happens:
|
|
159
159
|
|
|
160
160
|
```js
|
|
161
161
|
const [fn, teardown] = isolatedFunction(
|
|
@@ -169,7 +169,7 @@ const [fn, teardown] = isolatedFunction(
|
|
|
169
169
|
)
|
|
170
170
|
|
|
171
171
|
await fn()
|
|
172
|
-
// =>
|
|
172
|
+
// => DependencyUnallowedError: Dependency 'malicious-package' is not in the allowed list
|
|
173
173
|
```
|
|
174
174
|
|
|
175
175
|
> **Security Note**: Even with the sandbox, arbitrary package installation is dangerous because npm packages can execute code during installation via `preinstall`/`postinstall` scripts. The `--ignore-scripts` flag is used to mitigate this, but providing an `allow.dependencies` whitelist is the recommended approach for running untrusted code.
|
package/package.json
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
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.49",
|
|
6
6
|
"types": "src/index.d.ts",
|
|
7
7
|
"main": "src/index.js",
|
|
8
8
|
"exports": {
|
|
9
|
-
".": "./src/index.js"
|
|
9
|
+
".": "./src/index.js",
|
|
10
|
+
"./errors": "./src/errors.js"
|
|
10
11
|
},
|
|
11
12
|
"author": {
|
|
12
13
|
"email": "hello@microlink.io",
|
|
@@ -35,7 +36,7 @@
|
|
|
35
36
|
],
|
|
36
37
|
"dependencies": {
|
|
37
38
|
"@kikobeats/time-span": "~1.0.5",
|
|
38
|
-
"acorn": "~8.
|
|
39
|
+
"acorn": "~8.16.0",
|
|
39
40
|
"acorn-walk": "~8.3.4",
|
|
40
41
|
"debug-logfmt": "~1.4.0",
|
|
41
42
|
"ensure-error": "~3.0.1",
|
package/src/compile/index.js
CHANGED
|
@@ -41,4 +41,3 @@ module.exports = async (snippet, { tmpdir = tmpdirDefault, allow = {} } = {}) =>
|
|
|
41
41
|
|
|
42
42
|
module.exports.detectDependencies = detectDependencies
|
|
43
43
|
module.exports.transformDependencies = transformDependencies
|
|
44
|
-
module.exports.UntrustedDependencyError = installDependencies.UntrustedDependencyError
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
const { execSync } = require('child_process')
|
|
4
4
|
const $ = require('tinyspawn')
|
|
5
5
|
|
|
6
|
+
const { DependencyNameError, DependencyUnallowedError } = require('../errors')
|
|
7
|
+
|
|
6
8
|
const install = (() => {
|
|
7
9
|
try {
|
|
8
10
|
execSync('which pnpm', { stdio: ['pipe', 'pipe', 'ignore'] })
|
|
@@ -14,14 +16,6 @@ const install = (() => {
|
|
|
14
16
|
}
|
|
15
17
|
})()
|
|
16
18
|
|
|
17
|
-
class UntrustedDependencyError extends Error {
|
|
18
|
-
constructor (dependency) {
|
|
19
|
-
super(`Dependency '${dependency}' is not in the allowed list`)
|
|
20
|
-
this.name = 'UntrustedDependencyError'
|
|
21
|
-
this.dependency = dependency
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
19
|
const extractPackageName = dependency => {
|
|
26
20
|
if (dependency.startsWith('@')) {
|
|
27
21
|
const slashIndex = dependency.indexOf('/')
|
|
@@ -41,12 +35,19 @@ const extractPackageName = dependency => {
|
|
|
41
35
|
}
|
|
42
36
|
|
|
43
37
|
const validateDependencies = (dependencies, allowed) => {
|
|
38
|
+
// Always check for command injection, regardless of allow list
|
|
39
|
+
for (const dependency of dependencies) {
|
|
40
|
+
if (dependency.includes(' ')) {
|
|
41
|
+
throw new DependencyNameError(dependency)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
44
45
|
if (!allowed) return
|
|
45
46
|
|
|
46
47
|
for (const dependency of dependencies) {
|
|
47
48
|
const packageName = extractPackageName(dependency)
|
|
48
49
|
if (!allowed.includes(packageName)) {
|
|
49
|
-
throw new
|
|
50
|
+
throw new DependencyUnallowedError(packageName)
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
53
|
}
|
|
@@ -55,5 +56,3 @@ module.exports = async ({ dependencies, cwd, allow = {} }) => {
|
|
|
55
56
|
validateDependencies(dependencies, allow.dependencies)
|
|
56
57
|
return $(`${install} ${dependencies.join(' ')}`, { cwd, env: { ...process.env, CI: true } })
|
|
57
58
|
}
|
|
58
|
-
|
|
59
|
-
module.exports.UntrustedDependencyError = UntrustedDependencyError
|
package/src/errors.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
class IsolatedFunctionError extends Error {
|
|
4
|
+
constructor (message) {
|
|
5
|
+
super(message)
|
|
6
|
+
this.name = 'IsolatedFunctionError'
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
class DependencyNameError extends IsolatedFunctionError {
|
|
11
|
+
constructor (dependency) {
|
|
12
|
+
super(`Dependency '${dependency}' is not a valid npm package name`)
|
|
13
|
+
this.name = 'DependencyNameError'
|
|
14
|
+
this.code = 'EDEPENDENCYNAME'
|
|
15
|
+
this.dependency = dependency
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
class DependencyUnallowedError extends IsolatedFunctionError {
|
|
20
|
+
constructor (dependency) {
|
|
21
|
+
super(`Dependency '${dependency}' is not in the allowed list`)
|
|
22
|
+
this.name = 'DependencyUnallowedError'
|
|
23
|
+
this.code = 'EDEPENDENCYUNALLOWED'
|
|
24
|
+
this.dependency = dependency
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = {
|
|
29
|
+
IsolatedFunctionError,
|
|
30
|
+
DependencyNameError,
|
|
31
|
+
DependencyUnallowedError
|
|
32
|
+
}
|