WebExtension: Sandboxing eval

I have an extension where I need to evaluate user-written JavaScript code. Rather than just calling eval() on it, I want to execute it in some kind of sandbox.

Mostly because I’m using symlinked modules in my sources, which Firefox refuses lo load directly, I started coding with chrome first.

In chrome I can create an attach an <iframe sandbox="allow-scripts" src="blob:chrome-extension://..."></iframe> to the head of my background script, .postMessage() an MessageChannel port to it and start sending requests to have scripts evaluated in that sandbox. From within that sandbox the evaluated code has absolutely no chance of doing anything more destructive than messing up the inside of the sandbox (I have considered freezing every last element in the global scope to effectively prevent that …).
I’m pretty sure this is a perfect solution - which only works in Chrome so far.

The problem is that to set up the MessageChannel connection and to do the evaling, I obvoisly need to run some initial code in the sandboxed iframe.
The CSP in WebExtensions won’t let me do that. In chrome I can just concat some modules and functions into the markup in the Blob and it works. Firefox doesn’t like the inline script.
Linking scripts from the extension doesn’t work - and shouldn’t, it’s supposed to be a sandbox after all.
And sadly the 'unsafe-inline' directive is not allowed in the CSP, neither are 'nonce-*'es.
Moving the code into a blob:-url doesn’t help either.

The only thing I haven’t tried yet is adding a 'sha256-...' exception, but that would require some pretty annoying changes in my quire dynamic code.

For now I can only add 'unsafe-eval' to the CSP and eval directly in the elevated background page.

Is there any (other) way to run code in an isolated environment?

Here is the code that works in Chrome:

1 Like

After a good nights sleep, I found a working solution:

if (gecko && !inContent) { // Firefox doesn't allow inline scripts in the extension pages,
		// so the code inside the script itself is allowed by 'sha256-QMSw9XSc08mdsgM/uQhEe2bXMSqOw4JvoBdpHZG21ps=', the eval() needs 'unsafe-eval'
		html += `<script data-code="${ btoa(script) }">eval(atob(document.currentScript.dataset.code))</script></html>`;
	} else {
		html += `<script>${ script }</script></html>`;
	}
}

So you can still execute any script you want in the sandbox and don’t need to know the hash of it.
The only downside is that you need to allow 'unsafe-inline' globally, even if you only intend to eval() inside the sandbox.

@NilkasG were you able to find a solution what does not require adding unsafe-eval to CSP rules in Manifest?

According to manifest.json/content_security_policy at MDN Mozilla will not accept such extension to be listed at AMO:

[…] extensions with ‘unsafe-eval’, ‘unsafe-inline’, remote script, or remote sources in their CSP are not allowed for extensions listed on addons.mozilla.org due to major security issues.

were you able to find a solution what does not require adding unsafe-eval

Nope. I’m quite sure there is none (if you really need to eval scripts).

[…] extensions with ‘unsafe-eval’, ‘unsafe-inline’, remote script, or remote sources in their CSP are not allowed for extensions listed on addons.mozilla.org due to major security issues.

Well, that’s new. You might be able to fight them on that point. Generally blacklisting unsafe-eval without providing an alternative is not very open minded.

You might open a new bug to expose Components.utils.Sandbox(), Components.utils.evalInSandbox(), Components.utils.Sandbox.importFunction(), etc as a new WebExtension API.