Alternative to unsafe-eval


(Svalorzen) #1

My extension has recently been disabled due to the enabling of unsafe-eval in the CSP. The extension provides a shell-like environment for the web, and unsafe-eval was used as a very important part of the addon is to enable users to write their own commands. The commands are written inside a text field in the option page of the addon, and I don’t require to use eval anywhere else. This is very similar to the old Ubiquity addon, which was made by Mozilla.

Without this feature, the extension loses most of its reason of existence. Is there any way to enable users to write their own scripts in a safe way?


(Martin Giger) #2

You can have your own script parser that understands just the things you need. See something like esprime for an exsiting parser that would give you an AST that you can then handle.


(Svalorzen) #3

This seems a weird solution though. It’s equivalent to executing eval in the end, is it not? As the scripts would be written by the users, I do need a full js parser, and then I might as well use eval right?

Is there any way to obtain an exception for such a use case?


(Martin Giger) #4

It’s not the same, since it can’t do anything, it can only do what you permit it to do.


(erosman) #5

unsafe-eval/eval/blob are not allowed due to security issues. In rare cases, it can be allowed by Admin Reviewers only. If you wish to discuss the issues further, you can reach admin reviewers on IRC #addon-reviewers on irc://mozilla.org/


(Niklas Gollenstede) #6

One thing I am pretty sure you need to do to pass the review is to properly sandbox the call to eval, I don’t think that just calling eval in the background script will ever be accepted. (Besides, it is not a good idea because all your local abd global variables will be exposed, e.g. the a further down in the function. Stuff like that is pretty bad for forward compatibility.)

I do something similar in a not yet released extension (even though I think I will remove it, but that is mostly for UX reasons).
What I do is create an iframe with sandbox="allow-scripts", then send the code I want to eval to it. This lets you selectively choose the things you want to expose to the user supplied scripts, and there is no way for the scripts to escape the sandbox. Here is the code:


And the linked makeSandbox:


(Svalorzen) #7

Thanks for the tip, I’ll try to dissect your code later, as it seems it does quite a lot of things that are probably not useful to me (correct me if I’m wrong!).

A question on your approach: the user script I get should simply contain functions (added via an API I provide) which I index and then call as needed when the user wants to call them. So I guess I could give the iframe access to my API to register the functions, but then where would the functions reside when I want to actually call them? Should I have the iframe contain some messaging code so that I only get the names of the functions to register out of it, and then I can pass to it something like name-of-function + arguments, and get back the results of the functions?

Thanks in advance for the help.


(Niklas Gollenstede) #8

your code […] seems it does quite a lot of things that are probably not useful to me (correct me if I’m wrong!).

I think you should actually use most of it.

A question on your approach: the user script I get should simply contain functions (added via an API I provide) which I index and then call as needed when the user wants to call them. So I guess I could give the iframe access to my API to register the functions, but then where would the functions reside when I want to actually call them? Should I have the iframe contain some messaging code so that I only get the names of the functions to register out of it, and then I can pass to it something like name-of-function + arguments, and get back the results of the functions?

Pretty much. But you don’t need to do it explicitly. The Evaluator class takes care of most of it. Here is an example:

const evaluator = await new (await require.async('background/evaluator'))({ init: port => {
	// ... create stubs for all functions that should be exposed and
	// forward the calls o the `port` (which is only available in this function)
	window.openTab = url => port.request('browser.tabs.create', { url, });
}, });
// ... add the handlers that the stubs call
evaluator.addHandlers('browser.tabs.', browser.tabs); // should be as selective as possible

// and then as often as you need it:

userFunction = evaluator.newFunction('url', 'openTab(url)'); // pass user input here
value = await userFunction('https://example.com'); // args and return value as structured clones
userFunction.destroy(); // release the resources in the sandbox 

I re-wrote makeSandbox as class and Evaluator to inherit from it. The code should be clearer now.
And I moved the Evaluator file: https://github.com/NiklasGollenstede/wikipedia-peek/blob/master/common/evaluator.js


(erosman) #9

Note: unsafe-eval in the CSP is not allowed because eval() is not allowed.

Using eval() by any other means would be the same and subject to rejection.


(Niklas Gollenstede) #10

One would assume that if unsafe-eval gets allowed, it’ll only be allowed for extensions that do it securely.

And if this is not one of the rare cases (I think it should, because the extension really relies on it), my approach would also work to eval code in any content tab (which one does not matter at all, because of the sandboxing, and all the remote calls can easily be piped to the background script. It would be pretty inefficient, though, because the sandbox would need to be re-created all the time).


(erosman) #11

There are only a limited examples where eval() make sense and thus would be allowed.

One example is calculators where the entry by the user is evaluated, although even then that requires safeguards.

Giving access to users to enter arbitrary code can result in browser (or computer) crash and other complications (depending on how the code is written). Evaluating an endless loop will cause a crash.

eval() in content scope has the additional security risks of being hijacked by other scripts in the same content.

That is the reason, Admin needs to decide how safe it is before allowing it.


(Svalorzen) #12

The scripts of the users are already wrapped in such a way that if they timeout without responding the function is interrupted and aborted, so in theory there shouldn’t be any way to crash (this is also done since the scripts may query network resources that may take a while to respond).

I agree with the security risks and I will definitely try to alter the code in order to sandbox the eval code, but I will ask the admins first whether it is worth it.

Thanks a lot in advance for your responses, I really appreciate the help =)

I’ll let you know whether I can manage to make the extension allowed again!


(Niklas Gollenstede) #13

I’d be genuinely interested in an accepted example and its “safeguards” (against infinite loops. Do you have one?

Giving access to users to enter arbitrary [JavaScript] code can result in browser (or computer) crash.

Giving users access to the power button can result in an unexpected power down as well. We are not removing those.
I think the approach here should be to tell users, who are only able to handle a power button, to just stay away.

arbitrary code can result in browser (or computer) crash […] (depending on how the code is written).

No matter how the code is written, that should not be possible. The worst possible outcome should be the loss of browser data (if the APIs are available) or 100% CPU utilization in a limited number of processes. If a browser crashes the computer, then there is something wrong with the browser and/or computer/OS.

eval() in content scope has the additional security risks of being hijacked by other scripts in the same content.

Oh. Yeah. Doing it in a content context is worse in a lot of ways. But it does not require a custom CSP …

That wont work. Your timeout will not fire before the current task has completed. If that never happens, the timeout will never fire either. (It will help with slow asynchronous tasks, though.)


(erosman) #14

For example, in case of calculators, limiting the content to numbers and mathematical signs will prevent functions/methods/etc being evaluated.


(Niklas Gollenstede) #15

If your “mathematical signs” include ()+[]!, you are still Turing complete.

That is why I’d like to see an actual concrete example, as in implementation. Do you have one?


(erosman) #16

I said safeguards, to reduce not eliminate, the chance of problems.
That is the reason eval() should be avoided.


(Niklas Gollenstede) #17

Hm. I generally dislike have-baked solutions like that, but I see your point.

What about a Worker based solution, that you can simply kill after a timeout (which works here because it runs in a different thread)?
I don’t really know why I didn’t use a worker in the first place …


(erosman) #18

There is a way to make calculators considerably safe but with general JS and eval() not really. Whatever scope it runs on it can cause issues.

As they often say in programming circles, eval is evil. :wink:

Note: tabs.executeScrip() is also eval()


(Niklas Gollenstede) #19

As they often say in programming circles, eval is evil.

Yes. It is often said and often true, because it can cross the line between data and code. But in cases where the evaled string is meant as code from the start, that is IMHO not the case.

But I think you can really create an airtight sandbox with a Worker, worker-src blob:, some basic modifications of the worer scope (e.g. remove all references to importScripts) and a timeout. Worst case the script sends incorrect data (structured clones) or keeps one CPU thread busy for a limited time.
And it would have no further impact on the security of the rest of the extension. script-src remains unchanged.

Obviously assuming that the browsers sandboxing of JavaScript works in general and side channel attacks like Rowhammer don’t apply. Sufficiently short timeouts should help here as well.