nodejs / help

:sparkles: Need help with Node.js? File an Issue here. :rocket:
1.47k stars 282 forks source link

Run require(x) in a new VM sandbox #761

Closed ORESoftware closed 6 years ago

ORESoftware commented 7 years ago

I am looking at the VM module https://nodejs.org/api/vm.html

I'd like to essentially run this code in a sandbox:

let sandBox  = {foo:'bar'};
vm.createContext(sandBox);

vm.runInNewContext('function(){' +
    'const foo = require('./some-other-file');' +
'}', sandBox);

my question is - how do load another JS file and run that new JS file in a new global context? My guess is that require() is not available in the VM string code, so how can I load the code and pass the appropriate values to the file? (filename, dirname, module, etc).

ORESoftware commented 7 years ago

For example, if I do this:

const vm = require('vm');
const fs = require('fs');

const sandBox = vm.createContext({foo:'bar'});
const file = fs.readFileSync(__dirname + '/adjacent-file.js');

vm.runInContext(file, sandBox);

I get an error:

evalmachine.<anonymous>:3
const foo = require('./some-other-file');
              ^
ReferenceError: require is not defined
    at evalmachine.<anonymous>:3:15
    at ContextifyScript.Script.runInContext (vm.js:32:29)
    at Object.exports.runInContext (vm.js:64:17)
ORESoftware commented 7 years ago

Is this the best way to do it:

https://stackoverflow.com/questions/20899863/the-module-property-is-undefined-when-using-vm-runinthiscontext

and if so, why don't the Node.js docs demonstrate an example of this?

tniessen commented 7 years ago

If you need to access parts of the current scope, why do you use the vm module at all? (Just out of curiousity).

If you really want to do this, you will need to put a wrapper function around your code, which provides implementations of require, module etc. You might want to take a look at this function.

bnoordhuis commented 7 years ago

For more, hah, context: require() loads code in its home context, you can't use it with vm.runInNewContext() (well, you can, but it won't do what you want.)

refack commented 7 years ago

Question

what's wrong with adding require to the sandbox?

> var sandBox1 = vm.createContext({foo:'bar', require});
undefined
> vm.runInNewContext('const path2 = require("path"); path2.join(foo, "b")', sandBox1);
'bar\\b'
> path2
ReferenceError: path2 is not defined
ORESoftware commented 7 years ago

@tniessen I am looking to isolate code for the purposes of testing. I have multiple tests in the same process, and as opposed to forking processes, I am looking to the vm module to see if I can isolate shared scope.

refack commented 7 years ago

@tniessen I am looking to isolate code for the purposes of testing. I have multiple tests in the same process, and as opposed to forking processes, I am looking to the vm module to see if I can isolate shared scope.

@ORESoftware If you manage please share, I'm looking into improving core's test harness https://github.com/nodejs/node/issues/14214

ORESoftware commented 7 years ago

@refack yeah I will share - they are attempting to do the same thing over at AVA. Not sure how much success they have had so far.

https://github.com/avajs/ava/issues/1332

I'd like to be notified if anyone "gets this working" :)

Bartozzz commented 7 years ago

Got the same problem. I am creating a simple package manager for one of my nw.js apps - Qilin. I need to load plugins from user desktop directly into nw.js context. Basically it tries to require plugins from the app whereas it should look directly into plugin directories.


You can find the manager itself here, but here are some core parts. The lifecycle is very basic:

  1. Download package from GitHub to a local directory;
  2. Build downloaded package using NPM (npm i, npm prepare, …);
  3. Load package (grab the exported variable);
const data:Object = await readPackage(dir);
const file:string = await readFile(exe, "utf8");

const script = new vm.Script(file, {
  filename: exe,
});

return {
  createContext: (ctx) => {
    return vm.createContext({
      ...ctx,
      __filename: exe,
      __dirname: dir,
      exports: exports,
      require: require,
      module: module,
    });
  },
  runInContext: (ctx) => script.runInContext(ctx),
};

Error:

{ module.js:529
    throw err;
    ^

Error: Cannot find module 'merge-descriptors'
    at Function.Module._resolveFilename (module.js:527:15)
    at Function.Module._load (module.js:476:23)
    at Module.require (module.js:568:17)
    at require (internal/module.js:11:18)
    at /Users/Bartek/Documents/GitHub/qilin-manager/demo/packages/plugins/crawlerr-master/dist/index.js:7:25
    at ContextifyScript.Script.runInContext (vm.js:59:29)
    at Object.runInContext (/Users/Bartek/Documents/GitHub/qilin-manager/dist/lib/load.js:44:23)
    at Manager.load.then (/Users/Bartek/Documents/GitHub/qilin-manager/demo/index.js:21:24)
    at <anonymous> code: 'MODULE_NOT_FOUND' }
gireeshpunathil commented 6 years ago

is this still outstanding?

gireeshpunathil commented 6 years ago

closing due to inactivity, please re-open if it is still outstanding.

aggregate1166877 commented 2 days ago

Don't know if this will help anyone else, I'm using the following for an interactive shell tool I'm making:

const vm = require('vm');

// Create the VM context.
const context = vm.createContext(global);

// Bind require to the parent's require.
context.require = function(moduleName) {
  return eval(`require`)(moduleName);
}.bind(global);

// Run some code.
vm.runInContext(`
  const fs = require('fs');
  console.log(
    fs.readdirSync('/'),
  );
`, context);

This script can also require other JS files with both absolute and relative paths, and those scripts can require their own things as well.