I am making a sandbox environment for javascript. For this I create a webworker with this function:
(self
is a webworkers global context)
(() => {
Function("\"use strict\";\nlet self,fetch,globalThis;(async()=>{try{"+arbitrary_code+"}}catch{})").apply({});
return;
})();
With this, people are only able to use vanilla javascript with no access to any browser api's. Is there any way to defeat this sandbox, is it secure enough?
2 Answers 2
If you want to do something similar to online JavaScript sandboxes, where arbitrary code be executed while limiting the damage that code can cause, what you do is run the code in/from an isolated page inside a frame on a different domain. Doing it this way lets you take advantage of several safeguards that browsers come built-in:
- iframes have attributes that let you lock down what the page inside have access to (see
sandbox
andreferrerpolicy
). - iframes are bound to the Same-Origin Policy, preventing the framed content from a different domain from reaching into the embedding page.
- You can set Content Security Policy rules to limit what the parent and the framed page can do (e.g. prevent the embedded page from being embedded elsewhere, prevent parent page from embedding a page from somewhere else).
- Because the page is on a frame in a different domain, it's considered a third-party context and built-in browser protections like partitioned storage APIs kick in.
This does mean your page's domain is different from the frame's domain, and that your code would have to be transported between domains (e.g. store the arbitrary code in a shared DB for the sandboxed domain to fetch and render).
And as mentioned by KIKO Software, this doesn't prevent any other security issue outside of this scope (e.g. browser exploits, social engineering, security flaws on the embedding page, malicious extensions, etc.).
-
\$\begingroup\$ This is a really good option, and even allows me to mostly keep my existing code. Should also be easier on server-side rendering, and I would no longer have to use eval/Function! Thank you! \$\endgroup\$Nightfall– Nightfall2022年11月29日 13:08:26 +00:00Commented Nov 29, 2022 at 13:08
If you really want a water tight sandbox environment then you should look at using wasm. You can even run it in a webworker
There is (or should be) no way out of a wasm interpreter call without going through the functions you set up for communicating back to the wider code. There is no other way to hand control back over. The only real danger is when the wasm module puts itself into a infinite loop, but that's something your code didn't defend itself from either.
There are js to wasm compilers though there are better languages to use as the source language for this.
-
\$\begingroup\$ Great awnser, thank you! I looked into wasm for a while, but it has some issues specific to my project. That is why I ended up using webworkers in the end. \$\endgroup\$Nightfall– Nightfall2022年12月01日 08:43:30 +00:00Commented Dec 1, 2022 at 8:43
new Function('return this')()
would escape this and expose the global context. \$\endgroup\$