laverdet / isolated-vm

Secure & isolated JS environments for nodejs
ISC License
2.2k stars 154 forks source link

Getting a return value from a script that imports modules #394

Closed daniel-santos closed 1 year ago

daniel-santos commented 1 year ago
node v20.5.1
isolated-vm v4.6.0 (from npm)
container os node:current-bullseye pulled 2023-09-01
host os Gentoo Linux
arch AMD64

Following the advice from Issue #351, I put together a script, but I can't get a return value from a module synchronously

import { createRequire } from 'node:module';

function buildScript() {
  const require = createRequire(import.meta.url);
  const ivm = require('isolated-vm');
  const isolate = new ivm.Isolate({ memoryLimit: 32 });
  const context = isolate.createContextSync();
  const script = isolate.compileModuleSync("import {FOO} from \"./foo\"\nFOO;");
  console.log(`script: ${script}`);
  script.instantiateSync(context, (specifier, referrer) => {
    if (specifier == './foo') {
        console.log(`specifier: ${specifier}`);
        return isolate.compileModuleSync("export const FOO = 42;");
    }
  });
  return script;
}

const script = buildScript();
const result = script.evaluateSync();
console.log(`Result: ${result}`);

or asynchronously

const script = buildScript();
var result;
await script.evaluate().then((ret) => {
  result = ret;
  console.log(`result: ${result}`);
});
console.log(`Result: ${result}`);

The result is always undefined.

Having read Issue #332, I guess that's no surprise, but despite the fact that my first javascript code was in 1996, I've been away from it for a long time and I'm not sure how else to get a result back from my code when it's compiled as a module. I can get a result if I build it as a script, but I haven't been able to get module loading to work as a script. I tried to get dynamic imports working, but had no luck there either.

Perhaps this would be a good topic for an example in the README.md?

daniel-santos commented 1 year ago

AH HAH!! I finally figured it out! I hope this is helpful to some one else later. You still have to use a module (instead of a script) in order to have the resolveCallback, but I've used a Callback to return the value instead.

import { createRequire } from 'node:module';

const require = createRequire(import.meta.url);
const ivm = require('isolated-vm');
const isolate = new ivm.Isolate({ memoryLimit: 32 });
const script = isolate.compileModuleSync("import {FOO} from \"./foo\"\ncb(FOO);");
const context = isolate.createContextSync();
script.instantiateSync(context, (specifier, referrer) => {
  if (specifier == './foo') {
      return isolate.compileModuleSync("export const FOO = 42;");
  }
});

let result;
const jail = context.global;
jail.setSync('global', jail.derefInto());
jail.setSync('cb', new ivm.Callback((value) => {
  result = value;
}));

script.evaluateSync();
console.log(`Result: ${result}`);