sebastianwessel / quickjs

A typescript package to execute JavaScript and TypeScript code in a webassembly quickjs sandbox
https://sebastianwessel.github.io/quickjs/
MIT License
610 stars 16 forks source link

Submit Your Feature Requests and Ideas #10

Open sebastianwessel opened 4 months ago

sebastianwessel commented 4 months ago

If you have ideas, wishes, feature request and feedback - please add them in the comments below

poef commented 3 months ago

Can I also inject javascript objects from the main process into the QuickJS runtime? I'm building a node express based data server, where I want to allow clients to run javascript queries on that data (https://github.com/SimplyEdit/SimplyStore/)

sebastianwessel commented 3 months ago

Hey @poef

Yes, you can. See Data Exchange Between Host and Guest You can use env, and provide strings, numbers, arrays, objects and functions.

const { evalCode } = await createRuntime({
  env: {
    MY_PROCESS_ENV: 'some environment variable provided by the host',
    KV: {
      set: (key: string, value: string) => keyValueStoreOnHost.set(key, value),
      get: (key: string) => keyValueStoreOnHost.get(key),
    },
  },
})
guest271314 commented 3 months ago

Read standard input to V8's d8 (/proc/PID/fd/0) with WebAssembly. Right now I'm using QuickJS via os.system() https://github.com/guest271314/native-messaging-d8/blob/quickjs-stdin-read/nm_d8.js#L16, https://github.com/guest271314/native-messaging-d8/blob/quickjs-stdin-read/read_d8_stdin.js. If we can do this with WebAssembly we can get rid of os.system() which call sh.

sebastianwessel commented 3 months ago

@guest271314 thanks for your feedback. As far as I understand, if the "regular" deadline nodejs module is available inside of the quickjs runtime, it should solve the issue or? So, the code inside the sandbox would look like this


const readline = require('readline');

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
  terminal: false
});

rl.on('line', (line) => {
    console.log(line);
});

rl.once('close', () => {
     // end of input
 });
guest271314 commented 3 months ago

As far as I understand, if the "regular" deadline nodejs module is available inside of the quickjs runtime

Where does Node.js API's get in to your WASM QuickJS build?

The idea is to use the least amount of resources to read d8 shell standard input. QuickJS (quickjs-ng) is around 1.2 MB.

I was thinking I could use WebAssembly/WASI to read stdin to d8 using WebAssembly.compile().

guest271314 commented 3 months ago

I'm basically trying to do this https://github.com/guest271314/native-messaging-webassembly without a WASM runtime, using the built-in WebAssembly object alone - within d8. I created a different solution for Mozilla SpiderMonkey.

sebastianwessel commented 3 months ago

What I was meaning is, that you can provide functions and data from a regular Node.js host application, to the wasm guest system. Basically, you would read the standard-in in the host application and provide this data than to the guest system, as a copy. What you try is, to disable the isolation of the wasm, and to allow to access the host system directly. Kind of disable security and enhance WASI in the direction of wasix. In this project, this not wanted, as the JavaScript should run in an isolated sandbox, ensuring that it is not possible to break out, and access host functionality directly, in an uncontrolled manner.

guest271314 commented 3 months ago

What I was meaning is, that you can provide functions and data from a regular Node.js host application, to the wasm guest system.

It doesn't make sense to me to use 108.7 MB node executable that depends on V8 to read standard input to v8 at 37.9 MB.

I use deno and bun and qjs and tjs the same that I use node, so I don't think of node as a "regular Node.js application". node is just another JavaScript runtime in the JavaScript toolbox for me.

That's why I chose qjs at 1.2 MB to do the task.

I have been trying to do this using d8s readline(), though have not succeeded, yet.

I saw your work and this issue requesting features and decided to place a feature request.

In this project, this not wanted, as the JavaScript should run in an isolated sandbox, ensuring that it is not possible to break out, and access host functionality directly, in an uncontrolled manner.

I don't think it is possible to achieve that requirement. I have broken out of too many alleged "sandbox" to think for a moment that it can't be done in this case, too.

Thanks!

sebastianwessel commented 3 months ago

Hey, No worries, it's totally wanted to place such issues - even if I can't help here. Can ask you, what's the general use case you like to achieve?

guest271314 commented 3 months ago

I don't have a use case for running applications in a "sandbox".

I generally break out of sandboxes that folks try to set up.

For people interested in "sandbox" code we already have that with Worker and WebAssembly in and out of the browser and SharedWorker, Worklet interfaces in the browser.

digipigeon commented 3 months ago

Runtime Limits

  1. Set Limits Maximum Memory Max CPU Time - as a decimal between 0-1 1 being its allowed to consume 100% of the core/process that it is running on.

  2. Observability It should be possible to poll the sandbox to find out, how much memory is being consumed (either as a value or as a percentage), same as CPU time.

  3. Informative Errors If the sandbox is destroyed because it exceeds the max memory or the cpu is used too much there should be a clean informative error thrown (according to the new design it might be good to throw this at both the runtime.runSandboxed level as well as at the sandbox.evalCode level.

I don't think its possible to include anything at present like the --allow-net feature of deno in node. However instead of marshaling (to and from) a fetch replacement which imposes this limitation within javascript. It would be desirable to look towards seeing if there is any way possible to impose a limitation on the sandbox runtime. If it was a separate process, there are ways to do this from the OS. But I have not found out a compatible solution for worker threads. However if there was any possible way to do this, it would be very good.

sebastianwessel commented 3 months ago

Hey @digipigeon thanks for your feedback. There was a similar question recently Limit CPU and memory usage. The idea of polling from outside is interesting, but there is no simple working solution for it in node. As long as the eval function is running, the host side is kind of blocked. If there is no eval executed, it is already possible to get memory information. As the intention is, to have a highly controlled and isolated sandbox, even if it would be technically possible to allow direct net access, I don't think I will add it (at least per default). The current focus is, to provide an environment, which is as close as possible to node and similar runtimes.

digipigeon commented 3 months ago

Hi @sebastianwessel, would you consider implementing https://nodejs.org/api/worker_threads.html#performanceeventlooputilizationutilization1-utilization2 as an alternative to accomplish something similar?

sebastianwessel commented 3 months ago

@digipigeon Not directly in this library, as the focus is on providing a sandbox, data (de-)serialization, runtime compatibility inside the sandbox, and developer experience (DX).

The developer should be free to choose the method that fits best. My recommendation is to use libraries that are specialized for this, such as the poolifier-web-worker package, which is used in the Server Example here.

For instance, the usage in the browser might differ from that in the backend. In the browser, you might need only one sandbox, while in the backend, as many sandboxes as possible are required.

aashutoshrathi commented 3 months ago

Can there be fuel metering like in wasmtime? Since it's similar concept in terms of running JS in an isolated environment using some engine compiled to WASM

sebastianwessel commented 2 months ago

@aashutoshrathi as quickly is used, there is the option to do something like this:

‘‘‘typescript let interruptCycles = 0 runtime.setInterruptHandler(() => ++interruptCycles > 1024)

‘‘‘

aashutoshrathi commented 2 months ago

@sebastianwessel and I can use interruptCycles as fuel here?

sebastianwessel commented 2 months ago

Kind of - it highly depends on your use case and what you like to achieve I guess.

Personally, I do not see any real world use case, where it makes sense, to count such things, because in this case, you will need up front what is executed in the sandbox, to find the correct value. It is simply the wrong layer to control such things imo. When it comes to resource consumption, you probably would need to do it on the webassembly level. Meaning you would need to configure node/bun/browser to restrict the wasm resources.