isolated-vm icon indicating copy to clipboard operation
isolated-vm copied to clipboard

How to pass in an async function to an isolate and await it

Open justinmmott opened this issue 1 year ago • 3 comments

I just pushed 3.0.0 to npm with some new features which improve situations like this. The previous example could be rewritten like this:

const ivm = require('isolated-vm')
const isolate = new ivm.Isolate()
const context = isolate.createContextSync()

async function runCode() {
  const fn = await context.eval('(function untrusted() { return Promise.resolve(123); })', { reference: true })
  return fn.result.apply(undefined, [], { result: { promise: true } })
}
runCode().then(value => console.log(value))
  .catch(error => console.error(error))

Let me know if it works out for you~

Originally posted by @laverdet in https://github.com/laverdet/isolated-vm/issues/125#issuecomment-566750394

I saw this and was able to get this to work. However, I'm wondering how I can pass a async function into an isolate and run it within the isolate and return the results. For example,

const isolate = new ivm.Isolate();
const context = await isolate.createContext();
const code=`
(async function main() {
  const a = (await p()) + "2"; // throws an error saying that #<Promise> could not be cloned
  return a;
})`

async function runCode() {
  await context.global.set(
    "p",
    new ivm.Callback(
      async () => {
        console.log("started this");
        const b = new Promise((resolve, reject) => {
          setTimeout(() => {
            resolve("resolved");
          }, 500);
        });
        return await b;
      },
   { async: true }
  ));
const fn = await context.eval(code, { reference: true });
return await fn.apply(undefined, [], {
  result: { promise: true},
 });;
}

const res = await runCode()
console.log(res) 

Expected result: "resolved2"

attached are logs as well pictures of the code that is being run on my local machine with logging that was removed from the above versions image image

typeof p: function
p to string function context.global.set.isolated_vm_1.default.Callback.async() { [native code] }
started this
throwing error inside TypeError: #<Promise> could not be cloned.
     at (<isolated-vm boundary>)
     at main (<isolated-vm>:6:21)
res starting
c resolved

justinmmott avatar Aug 13 '22 16:08 justinmmott

Was able to get it to work with

class FetchWrapper {
  static methods = [this.fetch];

  static async fetch(url: RequestInfo, init?: RequestInit | undefined) {
    const response = await fetch.apply(this, [url, init]);
    const res = await response.text();
    return res;
  }
}
context.evalClosure(
      `
      fetch = function (url, init) {
          return $0.apply(undefined, [url, init], { arguments: { copy: true }, result: { promise: true } });
      }
`,
      FetchWrapper.methods,
      { arguments: { reference: true } }
    );

    const res = await context.eval(node.code, { promise: true });
const response = await fetch("http://url.com");
data = JSON.parse(response);
return data.param;

Ideally I wouldn't have to do the await response.text(); because it makes the passed in code different that just basic js, but works good enough

justinmmott avatar Aug 13 '22 19:08 justinmmott

The example can be even simpler:

The main thing is that when running you need to tell the context is a promise.

const isolate = new ivm.Isolate({ memoryLimit: 124 });
const isolateSource = 'code here';
const context = isolate.createContextSync();
const hostile = isolate.compileScriptSync(isolateSource );
await hostile.run(context, {promise: true});

stanimirovv avatar Aug 14 '22 13:08 stanimirovv

@justinmmott are you able to do context.global.set with the fetch wrapper that you've created? If so do you mind sharing a code snippet of how you achieve that? Thanks in advance!

yungcheng avatar Aug 23 '22 17:08 yungcheng