patriksimek / vm2

Advanced vm/sandbox for Node.js
MIT License
3.86k stars 295 forks source link

How to allow setTimeout() in VM2 NodeVM? #508

Closed trasa closed 1 year ago

trasa commented 1 year ago

Hi, I know most of the time people want to disallow sneaky evil things like timeouts in their scripts, but in this case I want to whitelist setTimeout(fn, millis). Things I've tried that haven't worked:

  sandbox.setTimeout = setTimeout;

(returns instantly)

and creating hackySetTimeout, which does while(true) until the time returned by process.hrtime() tells us its been long enough (this does not have to be performant or accurate to better than a second). This works, but breaks other things due to the busy-loop.

So, before I get into figuring out why the busy-loop is breaking an unrelated thing (sigh, another bug somewhere else in my code no doubt) -- is there some other way to allow the built in setTimeout()?

My current code looks something like this, and I feel like I'm missing something obvious but google hackery has come up short.

import {NodeVM} from 'vm2'; 
export const sandbox = {};
sandbox.pm = new PM();
sandbox.setTimeout = setTimeout; // hackySetTimeout;

export function createVM() {
    return new NodeVM({ 
        console: 'inherit',
        sandbox: sandbox,
        require: { external: true, root: './'}
    });
}

thanks!

XmiliaH commented 1 year ago

The default NodeVM exposes a working setTimeout function into the sandbox. The following script runs for me and prints Timeout after one second.

const vm = new NodeVM({
    console: 'inherit',
    require: { external: true, root: './'}
});
vm.run('setTimeout(()=>console.log("Tiemout"), 1000);');
trasa commented 1 year ago

Hmm. Yes, your example code works perfectly. My problem must lie elsewhere.... (sigh) onwards!

thanks! Tony

XmiliaH commented 1 year ago

(returns instantly)

Just as a quick note: setTimeout returns instantly. The callback is later called after the delay.

trasa commented 1 year ago

Yes, I think that is one of the problems with the script being run, and will have to be dealt with. Another issue, that isn't so much a VM2 problem but worth noting is that vm.run(s) ~returns immediately as well~, but the problem code (not shown here) is expecting it to be finished before continuing.

edit: I'm not explaining myself well... the vm.run() returns "immediately" because setTimeout() returns "immediately". Theres several layers of 'this js code is flawed' here, and not related to vm2.

Simple Example: (wrapped in a Commander action)

 program
        .command('donothing')
        .action(async () => {
            console.log("start");
            let vm = createVM();
            let x = "setTimeout(()=> { console.log('timeout happened')}, 5000);";
            await vm.run(x);
            console.log("done.");
        });

As you can imagine, this prints out:

start
done.
timeout happened