10 ms·
Show HN: Run unsafe user generated JavaScript in the browser
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/ https://workerbox.net/
Github: https://github.com/markwylde/workerbox https://github.com/markwylde/workerbox
- oso2k 4y agoOne upon a time, Crockford worked on ADSafe for a similar goal. https://www.crockford.com/adsafe/ https://www.crockford.com/adsafe/
- naugtur 4y agoSES-shim is the modern successor to that by Doug's old pal Mark and team. https://github.com/endojs/endo/tree/master/packages/ses https://github.com/endojs/endo/tree/master/packages/ses Also, on its way to being standardized in tc39
- deleted 4y ago[deleted]
- rootw0rm 4y agoOnce upon a time there was a private retro gaming BitTorrent tracker called UG. They had a few browser games, one of which was blackjack. I wrote a greasemonkey script to play blackjack for me unattended, folks were not amused =(
- jitl 4y agoIf you need to call into user-generated Javascript synchronously or have greater control over the sandbox environment, you can use WebAssembly to run a Javascript interpreter: https://github.com/justjake/quickjs-emscripten#quickjs-emscripten https://github.com/justjake/quickjs-emscripten#quickjs-emscr... QuickJS 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-sync-in-quickjs https://github.com/justjake/quickjs-emscripten#async-on-host...
- m1sta_ 4y agoNobody has benchmarked postMessage vs QuickJS?
- laurencerowe 4y agoI believe SharedArrayBuffer and Atomics.wait allow the same synchronous access within the worker to async functions in the host. This is the approach used by PartyTown: https://partytown.builder.io/how-does-partytown-work https://partytown.builder.io/how-does-partytown-work
- idealmedtech 4y agoI know the industry has moved on to WebAssembly, but I thought it might be worth mentioning that I just recently finished a personal project to compile an arbitrary NodeJS script in QuickJS and call the code from C - all* in a statically or dynamically linked library, no support files. In case anyone is interested in calling JS from old school C-supported languages, check it out: https://github.com/ijustlovemath/determine-basal-native 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.
- ehhthing 4y agoYou can achieve the same thing with iframes using the "sandbox" attribute.
- arkadiyt 4y agoFigma has a great blog post on some different user-generated javascript isolation approaches [1]. They discuss this iframe approach, running a webassembly javascript interpreter (also suggested in these thread comments), and using javascript realms. In the post they originally opted for javascript realms but there's an edit at the top - it seems like they ended up switching to the webassembly javascript interpreter approach. [1]: https://www.figma.com/blog/how-we-built-the-figma-plugin-system/ https://www.figma.com/blog/how-we-built-the-figma-plugin-sys...
- turblety 4y agoThanks for sharing the link. That was an amazing read. I originally tried the proxy method (I didn't know about Realms though, they seem cool), and I think I got pretty far. At least, I couldn't find a way to get to the constructor, and access the main scope. 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!
- naugtur 4y agoAgoric moved forward and Realms gave way to SES https://github.com/endojs/endo/tree/master/packages/ses 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
- xmonkee 4y agoOh, dude! Genius. I needed something exactly like this. Is there a similar lightweight approach in node land? 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.
- turblety 4y agoThere is vm2 [0] for nodejs, but if you look at the issues there are escapes found occasionally, even some this year. I think it's too risky to run untrusted code on node. 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 https://github.com/markwylde/vm2-process 1. https://github.com/jdorn/json-editor https://github.com/jdorn/json-editor
- AshleysBrain 4y agoThe upcoming JavaScript Shadow Realms proposal looks like it solves a similar problem: https://github.com/tc39/proposal-shadowrealm/blob/main/explainer.md https://github.com/tc39/proposal-shadowrealm/blob/main/expla...
- turblety 4y agoRealms look really amazing. I'm going to keep a close eye on this. Stage 3 sounds promising.
- white_dragon88 4y agoToo bad the name sounds idiotic. To even mention it to anyone even in the engineering department is going to make me sound like a super nerd.
- hyuuu 4y agobut like, your username tho
- naugtur 4y agoThere's a related proposal for Compartments and Module constructor is a prerequisite to that. A shim for the entire thing exists, with lockdown and Compartments isolating code: https://github.com/endojs/endo/tree/master/packages/ses https://github.com/endojs/endo/tree/master/packages/ses https://github.com/tc39/proposal-compartments/ https://github.com/tc39/proposal-compartments/ It has usage already, eg. metamask snaps
- nubb 4y agoopen invite for security researchers to test this app?
- spiffytech 4y agoWhat advantages does workerbox have over existing solutions like jailed[1]? [1] https://github.com/asvd/jailed https://github.com/asvd/jailed
- gernb 4y agoGoogle used to have Caja https://github.com/googlearchive/caja https://github.com/googlearchive/caja
- cromwellian 4y agoEndojs/SES is the latest and if you look at the list of things it has to do to make JS safe, you’d feel nervous about any approach that doesn’t use an interpreter and process isolation. Side channel attacks against CPU caches are still a real issue in JS.
- yonixw 4y agoPlease note a function like `fetch` still works, and might be privacy (and security!) issues in this method.
- russellbeattie 4y agoI'll chime in with my random bit of knowledge about a similar topic. Chrome has a bug where if you dynamically create an iframe (document.write(), .createElement(), .innerHTML, etc.), the parent page's Service Worker isn't inherited. It just fails. However, if you use a blank page loaded via the src attribute coming from the same domain, it works as expected. Service workers are really nothing more than client-side network proxies, so once you've loaded that blank page, you can do whatever you want to it (add styles, images, scripts, what have you), then use the service worker to control its access to the network. I use this in an editor app to seamlessly load local assets by intercepting the iframe's network requests. I'm not going for strict security as there's no server-side data involved, so I'm not sure how secure this approach would be for user generated content, I just wanted to toss it out there for those interested. It might save you some time at some point. (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#c15 https://bugs.chromium.org/p/chromium/issues/detail?id=880768...
- Nican 4y agoThis is actually what the Azure Portal does [1] for embedding 3rd party applications. They even developed a custom HTML language to describe how to layout applications to give a consistent look. 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/generated/portalfx-blades-appblades.md https://github.com/Azure/portaldocs/blob/main/portal-sdk/gen...
- turblety 4y agoAhh really interesting. Yeah I think (hope?) one benefit of using a web worker should be if anyone writes some blocking code, it will not hold up the main web page thread. Also, as web workers can be terminated, if they run too long the main thread can just kill it. 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.
- akomtu 4y agoWhat's the difference between safe and unsafe JavaScript?
- j33zusjuice 4y agoWow. The article did not match the expectations I got from the title. I thought this was a joke that allows you to run dangerous code that browsers would normally block by default or something.
- green_man_lives 4y agoI did something similar but without needing web workers, iframes, or any type of vm. I used Proxy to intercept Function and hide all scopes outside of the closure. I don't know if it's 100% safe, but it's just for a small game demo, letting users control in-game units in the browser. https://ai-arena.com/ https://ai-arena.com/ If anyone knows how to break my game, please let me know!
- 19h 4y agoBut have you tried compiling V8 to WebAssembly to run the JS inside a VM within JS? /s
- apineda 4y agoI believe Figma does this. Considering the lack of options it ain't so bad. Other options include running an iframe, or using something like `ses` library. I do both depending on the plugin. As always the biggest pita is sharing instances of anything at all sigh.
- turblety 4y agoIt's actually possible to do it with a library called Duktape [0]. But yeah, I get it would be super slow, and create a huge wasm file for the client to download. 0. https://duktape.org/ https://duktape.org/
- AlexErrant 4y agoNice, I'm building a plugin system for my webapp too! I gave up completely on security [0] though because my plugins want DOM access, and may possibly manipulate elements outside their specific control, so I was like "fuck it". Thankfully there's a clear difference between my webapp and website, so hopefully I'll be able to educate my users to not trust stuff on the app side. We'll see :| 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-decisions/frontend.md#plugins https://github.com/AlexErrant/Pentive/blob/main/design-decis... 1: https://github.com/AlexErrant/Pentive/blob/main/app/src/plugin-manager.proof-of-concept.ts https://github.com/AlexErrant/Pentive/blob/main/app/src/plug... 2: https://github.com/AlexErrant/Pentive/blob/main/app/src/plugin-manager.ts https://github.com/AlexErrant/Pentive/blob/main/app/src/plug...
- turblety 4y agoI'm not too sure right now about the design of the plugin system, but I'm thinking it'll just be setting a bunch of JSON data, declaring how elements/menus/pages will behave. Still need to work on this part.
- TheDesolate0 4y ago
- deleted 4y ago[deleted]
- s3000 4y ago> in a web worker on a separate domain Does it need a separate domain for every script to prevent two or more user generated scripts from influencing each other?
- turblety 4y agoIt's a really good question, and I originally [0] actually had some code that would randomise the sub domain. You can still do this if you host it yourself. 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.js#L29 https://github.com/markwylde/workerbox/blob/master/lib/index...
- wes-k 4y agoReally awesome!! Just this week I was wondering how I was going to approach this for a project of mine. I recalled the figma article but your approach seems simpler. I’ll definitely give this a shot! Thank you!!
- nullzzz 4y agoThis came really handy, as I was in need for a way to run sandboxed user code in a coding game! 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.
- kev_emu 4y agoAnother example is the sandbox we have built for Peergos[1] for user apps Combination of double iframe, service worker and writable streams. Not without issue due to browser behaviour. Chrome (...annoyances), Firefox (...feature not necessarily available), Safari (...blocking bugs) [1] https://peergos.org/posts/a-better-web https://peergos.org/posts/a-better-web
- cheri9 4y ago[dead]