Show HN: Run unsafe user generated JavaScript in the browser (workerbox.net)
I needed a way to let users write JavaScript to create plugins for a site I'm building.
I couldn't find a solution I was happy with, so ended up building one that run's it in a web worker on a separate domain from your main site.
Hopefully I haven't missed anything. If so, please let me know!
My website has an interactive demo you can write code in the browser textarea, and see the output on the right.
Interactive Demo: https://workerbox.net/
45 comments
[ 1.9 ms ] story [ 103 ms ] threadQuickJS in WebAssembly is much slower than your browser's native Javascript runtime, but possibly faster than async calls using postMessage. As an added bonus, it can make async functions in the host appear to be synchronous inside the sandbox using asyncify: https://github.com/justjake/quickjs-emscripten#async-on-host...
In case anyone is interested in calling JS from old school C-supported languages, check it out: https://github.com/ijustlovemath/determine-basal-native
It's specific to my application but could easily be reused with a few tweaks.
* Mostly all, the last step is to store the ESM JS script text in an object file and link it in, which I'm still working on.
https://www.crockford.com/adsafe/
Also, on its way to being standardized in tc39
[1]: https://www.figma.com/blog/how-we-built-the-figma-plugin-sys...
But I'm just not confident you can really isolate code on the same domain that your main code run's in. That's why I ended up moving to a web worker on a different origin. It also comes with the plus that you can kill a hanging web work, as it's run in a different process.
Going to be keeping a close eye on Realm though!
https://github.com/endojs/endo/tree/master/packages/ses
And Endo is a set of tools (being) built around it to make it more practical for particular usecases
Edit: my usecase is a form builder where users can specify fields that have their values computed from other fields. I've had to hobble it by using templating languages for the custom code, or just let them use javascript and be okay with the risk since the users get their own subdomain, so it's kinda like their own website.
You could run a headless chrome using puppeteer, then run workerbox. But it's probably too much overhead for a server app.
Oh, if you're making a form builder, I have to recommend json-editor [1]. It's not mine and I haven't actually used it just (I've only found really recently), but basically you give a JSONSchema and it will make a form for you, with validations and conditionals all built in. Might come in useful.
0. https://github.com/markwylde/vm2-process
1. https://github.com/jdorn/json-editor
https://github.com/endojs/endo/tree/master/packages/ses
https://github.com/tc39/proposal-compartments/
It has usage already, eg. metamask snaps
[1] https://github.com/asvd/jailed
https://github.com/googlearchive/caja
Side channel attacks against CPU caches are still a real issue in JS.
(I think it would be wonderful if the Chrome guys fixed this 2+ year old bug so the rest of us don't have to spend a week or so trying to figure out WTF is going on before realizing the obvious-in-retrospect workaround. Just saying.)
https://bugs.chromium.org/p/chromium/issues/detail?id=880768...
The downside was the portal started to become slow, and sometimes would have 20+ iframes open.
[1] https://github.com/Azure/portaldocs/blob/main/portal-sdk/gen...
I'll have to read up more about the portal-sdk to see if there's any other stuff on there that could be helpful building a plugin system. Thanks.
https://ai-arena.com/
If anyone knows how to break my game, please let me know!
But yeah, I get it would be super slow, and create a huge wasm file for the client to download.
0. https://duktape.org/
Separately, how are you thinking about designing your plugin system? I built a PoC here [1] that basically revolves around combining a DI container with the decorator pattern. We start with an initial default DI container/object, then that container is passed to a plugin which wraps certain methods or adds new ones. That new container is then passed to the next plugin which wraps it, etc, etc. Actual implementation is here [2]. The only thing that sucks about my design is that plugins must call `.bind(this)` when wrapping a method. So basically plugins have unfettered access to my DI container. Perhaps I'm giving plugins too much power? :shrug_shoulders:
0: https://github.com/AlexErrant/Pentive/blob/main/design-decis...
1: https://github.com/AlexErrant/Pentive/blob/main/app/src/plug...
2: https://github.com/AlexErrant/Pentive/blob/main/app/src/plug...
Does it need a separate domain for every script to prevent two or more user generated scripts from influencing each other?
But I think it's probably not nessisary so long as your users are not using indexDB (or any other data storage on that domain), as there should be no other way for the web workers to communicate with each other.
0. https://github.com/markwylde/workerbox/blob/master/lib/index...
Another cool thing with WebWorkers is that the code is run in a separate thread and allows you to terminate it if it runs into an infinite loop, for instance.