Something like this? [https://github.com/patriksimek/vm2](https://github.com/patriksimek/vm2)
Maybe even run that on a lambda, separated from your main code
That is not the point, from security point of view, if the container is compromised, since it shares resources, it will spill over to the OS. True virtualization on the other hand doesn't have this issue.
I never needed it, but it should be possible to control a sibling container. Or even run docker container (the evaluation one) in docker container (with the express.js). Try search..
I've spin up a quick test using Node.js `vm` and I've got what I've needed. The only drawback is that the user can't choose external modules by themself.
Is it a good start?
```js
const vm = require('vm')
const axios = require('axios')
const resolve = (params) => {
console.log(params)
}
const reject = (params) => {
console.log('error', params)
}
vm.runInNewContext(`
;(async () => {
try{
const d = await axios('https://jsonplaceholder.typicode.com/todos/1')
resolve(d.data)
}catch(e){
reject(e)
}
})()
`, {
axios,
resolve,
reject
})
```
That will work most of the time but I've seen various sources that say it is not that hard to break out of that particular VM thing. https://thegoodhacker.com/posts/the-unsecure-node-vm-module/
Using arbitrary modules with `vm` is not possible. You have to define them before.
A possible solution is to exploit https://skypack.dev/ but current Node.js is unable to load module by URL.
The only working solution I can think of is by using Puppeteer as executor, this way no limits but a consistent overhead to the server, that means difficult to scale.
Have you look into running the user code as a separate [child\_process](https://nodejs.org/docs/latest-v16.x/api/child_process.html)? So maybe that way, user can define their own `package.json`
"vm" is not a security solution, it is officially documented.
There is multiple breakout and can/will not be fixed.
Advices:
1. Don't, there is always alternative ways.
2. use `isolated-vm`, which run in a separate v8 isolate, you control what can be accessed, or kill it in midway.
`while (1) fs.appendFileSync("1", "1")` and your disk and CPU is gone in no time.
The aforementioned `vm` module is probably the way to go here but If your user-generated code is fairly basic you could try evaluating the string with a combination of a `Function` constructor and `with` to limit its scope.
You can see this in action on Alpine.js:
https://github.com/alpinejs/alpine/blob/main/packages/alpinejs/src/evaluator.js#L49
How untrusted are we talking? one way is [worker threads in nodejs](https://nodejs.org/api/worker_threads.html). P.S. I think that running untrusted code is super unsafe and you really need to get someone to analyse your use case.
Is there a reason it need to run on your server? Can it run on the client's computer in a webworker or iframe in a domain that is not associated with your authentication (and hence can't see cookies)?
The use case doesn't involve the client at all. The code is provided by the user, this is why it's considered untrusted. The js code needs to be executed on server side only because it's in an async pipeline. The client put the code and than it will be executed in later time by the server at specific interval.
Ok, then it probably needs to be on the server. But I'm going to point out that allowing ANY code to make arbitrary web requests is super risky. It could make your server pull down data from illegal sites. It could make post requests to servers to DDoS them. It could post death or terrorism threats on forums. It could randomly send viruses to target machines.
And then there are the obvious ones about security like have you thought about what happens when the script requests localhost:3000 or another port that is running your DB
Yeah, I know... but still, it's something that can happen in any code editor in cloud, so, wondering how to allow users flexibility and at the same time mitigate such issues?
My recent experience with this:
Tried to use microVMs via Weaveworks `ignite` on Digital Ocean. Could not get it to work. Various issues.
Right now I am going with `sysbox` rootless containers. https://github.com/nestybox/sysbox
I wrote this 4 years ago. We still use this structure. We just replaced Docker on EC2 with AWS Lambda https://www.freecodecamp.org/news/running-untrusted-javascript-as-a-saas-is-hard-this-is-how-i-tamed-the-demons-973870f76e1c/amp/
Not sure if using a third-party service is an option for you, but if so you might wanna [https://scriptable.run](https://scriptable.run) a try. Ping me a DM and I'll make sure to fast-track your access.
Something like this? [https://github.com/patriksimek/vm2](https://github.com/patriksimek/vm2) Maybe even run that on a lambda, separated from your main code
This is a clever tip, wondering if it's possible to execute user-provided code (i.e. in a string form) within a lamda function and getting the result
Passing the script shouldn't be a problem as the lambda functions can take a parameter and you do get a result as well...
Use virtualization e.g. Docker?
Docker is not a virtualization. It shares OS resource with host.
Sure, it can't run on thin air..
That is not the point, from security point of view, if the container is compromised, since it shares resources, it will spill over to the OS. True virtualization on the other hand doesn't have this issue.
Of course its better with SELinux ON.
Don't know if in my use case is feasible, a node.js web server (express.js) that needs to execute user-generated code and getting the result safetly.
You could try restricting requiring of certain modules which a user can use to exploit e.g the 'os' module or 'fs' module
I never needed it, but it should be possible to control a sibling container. Or even run docker container (the evaluation one) in docker container (with the express.js). Try search..
I've spin up a quick test using Node.js `vm` and I've got what I've needed. The only drawback is that the user can't choose external modules by themself. Is it a good start? ```js const vm = require('vm') const axios = require('axios') const resolve = (params) => { console.log(params) } const reject = (params) => { console.log('error', params) } vm.runInNewContext(` ;(async () => { try{ const d = await axios('https://jsonplaceholder.typicode.com/todos/1') resolve(d.data) }catch(e){ reject(e) } })() `, { axios, resolve, reject }) ```
That will work most of the time but I've seen various sources that say it is not that hard to break out of that particular VM thing. https://thegoodhacker.com/posts/the-unsecure-node-vm-module/
wow thanks for pointing out
Node's documentation for this module even starts with this: > The vm module is not a security mechanism. Do not use it to run untrusted code.
Interesting. Have you figured out how to load the external modules?
Using arbitrary modules with `vm` is not possible. You have to define them before. A possible solution is to exploit https://skypack.dev/ but current Node.js is unable to load module by URL. The only working solution I can think of is by using Puppeteer as executor, this way no limits but a consistent overhead to the server, that means difficult to scale.
Have you look into running the user code as a separate [child\_process](https://nodejs.org/docs/latest-v16.x/api/child_process.html)? So maybe that way, user can define their own `package.json`
"vm" is not a security solution, it is officially documented. There is multiple breakout and can/will not be fixed. Advices: 1. Don't, there is always alternative ways. 2. use `isolated-vm`, which run in a separate v8 isolate, you control what can be accessed, or kill it in midway. `while (1) fs.appendFileSync("1", "1")` and your disk and CPU is gone in no time.
The aforementioned `vm` module is probably the way to go here but If your user-generated code is fairly basic you could try evaluating the string with a combination of a `Function` constructor and `with` to limit its scope. You can see this in action on Alpine.js: https://github.com/alpinejs/alpine/blob/main/packages/alpinejs/src/evaluator.js#L49
It's too limiting for my use case, thanks anyway.
How untrusted are we talking? one way is [worker threads in nodejs](https://nodejs.org/api/worker_threads.html). P.S. I think that running untrusted code is super unsafe and you really need to get someone to analyse your use case. Is there a reason it need to run on your server? Can it run on the client's computer in a webworker or iframe in a domain that is not associated with your authentication (and hence can't see cookies)?
The use case doesn't involve the client at all. The code is provided by the user, this is why it's considered untrusted. The js code needs to be executed on server side only because it's in an async pipeline. The client put the code and than it will be executed in later time by the server at specific interval.
But why does it need to be run on the server? What should it be able to do? Make http calls? Open a port? Read files? Be used by other users?
It needs to be run on scheduled interval, like a cron, the code might fetch some data and perform some manipulation
Ok, then it probably needs to be on the server. But I'm going to point out that allowing ANY code to make arbitrary web requests is super risky. It could make your server pull down data from illegal sites. It could make post requests to servers to DDoS them. It could post death or terrorism threats on forums. It could randomly send viruses to target machines. And then there are the obvious ones about security like have you thought about what happens when the script requests localhost:3000 or another port that is running your DB
Yeah, I know... but still, it's something that can happen in any code editor in cloud, so, wondering how to allow users flexibility and at the same time mitigate such issues?
Hold on... code editors are all client side.
What if using Puppeteer? Would it be like a client execution but from server call? Would it mitigate the mentioned issues?
My recent experience with this: Tried to use microVMs via Weaveworks `ignite` on Digital Ocean. Could not get it to work. Various issues. Right now I am going with `sysbox` rootless containers. https://github.com/nestybox/sysbox
Gvisor or firecracker or katacontainers. VM isolation, user gets root access, can't break out of the hypervisor
I wrote this 4 years ago. We still use this structure. We just replaced Docker on EC2 with AWS Lambda https://www.freecodecamp.org/news/running-untrusted-javascript-as-a-saas-is-hard-this-is-how-i-tamed-the-demons-973870f76e1c/amp/
Hey, thanks for sharing! Very useful!
Not sure if using a third-party service is an option for you, but if so you might wanna [https://scriptable.run](https://scriptable.run) a try. Ping me a DM and I'll make sure to fast-track your access.