MathJax requires unsafe "Content Security Policy"

Created on 11 Jun 2012  ·  42Comments  ·  Source: mathjax/MathJax

The current MathJax implementation uses following features that are not considered safe in modern world and cannot be used with default Content-Security-Policy (http://www.w3.org/TR/CSP/) headers:

  • JavaScript evaluation of strings (new Function() with a string or eval()) (1)
  • Inline style attributes inserted via JavaScript (2)

It's debatable if issue (2) should be fixed but at least (1) should be fixed because Content-Security-Policy does not have enough granularity to allow MathJax to execute as a trusted script and in the same time do not interpret every other JavaScript file as specially trusted. Currently, if one wants to use MathJax, he must allow eval() everywhere. The issue (1) also causes bug #130 (MathJax is incompatible with ECMAScript 5 strict mode).

Currently the only way to make MathJax to work, even if one uses locally installed MathJax code, is to use following CSP HTTP headers (the deprecated "options" header is included for Firefox 13.0 and lesser):

X-Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; options eval-script
X-WebKit-CSP: default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'

These headers will allow some CSP provided protection and still allow MathJax to work, if MathJax is distributed from the same origin as the page content.

Accepted Fixed Test Not Needed v2.4

Most helpful comment

@kaushalmodi, see my comment above about how to avoid the problem, or my comment to the issue you link to. You have to change how you configure MathJax if you are using a CSP that restricts script execution.

All 42 comments

unsafe eval is a big issue for me as well. Very soon chrome will force all extensions to adopt a content security policy that bans the use of eval. We are currently trying to upgrade Readium to comply with this but we cannot use do so and use MathJax

+1

Thanks guys. We'll look into it.

Just a preliminary update. It seems possible to fix some of the issues soon enough (hopefully enough for the Chrome store). However, we're not sure how the performance will suffer -- which, as you can guess, could be bad. We'll keep you updated when @dpvc can look at this in more detail and perhaps create some experimental branches.

If you have additional comments, questions and suggestions about specific code, please let us know.

I spent a bit of time looking into it myself. It seems like you guys need to stop with the premature optimization. Evaling a string is NOT faster than creating a closure.

Also, you shouldn't be running eval() on every page that loads Mathjax just to check if you can run eval(). Basically, this needs to go.

The new Function() calls are not for speed but for memory. The new Function() doesn't create a closure, and so doesn't hold onto the local scope. Since this is at the heart of the object-oriented programming model, and there are lots of objects in use, it could have an impact on memory usage. I haven't done any testing of that in recent browsers.

As for eval usage, MathJax does use it to handle its in-line configuration blocks, and that could not be easily replaced at this point. So a "safe" version of MathJax would need to use external configuration files (which should not be a problem, and probably is more in line with the security policy anyway). The eval call you refer to is not to determine if eval can be run, but to determine if it runs in the global namespace (which isn't the case in all browsers). I'm aware that this would need to be removed for a version that would work with your security needs.

That is not how closures work in javascript. You do not hold on to everything in local scope; you only capture things that your function uses. Your CONSTRUCTOR function uses nothing from the local scope there is no cost to creating the closure. This is premature optimization for a problem that never existed.

As far as the eval thing is concerned my point is that you should not be running code on a page if you don't need to, especially if that code that uses unsafe eval(). You only use that EVAL function if Mathjax has been included with in-line configuration so, don't run the test for it until you need to. But really, why on earth does the configuration have to be executable code. It is really just a chunk of JSON that is passed to function. Why not just pass the JSON, parse it and have MathJAX call the function?

That is not how closures work in javascript. You do not hold on to everything in local scope; you only capture things that your function uses.

I don't think things are as clear cut as that. First, this was _not_ the case back in 2008 when that part of the code was written. I have just run tests this morning that appear to confirm that the versions of Safari, Firefox, Opera, and IE in play at the time all worked the way I described (the complete scope chain was retained in a closure, regardless of the variables used). The site that is my reference for this seems to be down this morning (was up yesterday), so I can't check details, but my recollection is that this was part of the ECMAScript 262 specification.

Current versions of Safari, Chrome, Firefox, and IE do appear to work as you describe, so somewhere since then things have changed. It looks like Safari 4, Firefox 3.6, and IE9 were where the change occurred; I don't have old versions of Chrome to test, so don't know the history there. IE8 has the older behavior, and Opera 12 still does. I have not checked mobile devices. Some of these browsers are on MathJax's support list, so it is still an issue that we have to consider for MathJax in general. Of course, I'm sure something can be worked out for your needs.

As for JSON data, the configuration block may be more than just a MathJax.Hub.Config() call. For example, you can install event listeners, or add functions to the TeX input jax to implement additional commands, and so on. These can't be part of JSON data because they include executable code. This feature is not always used, but it certainly _is_ being used by actual web sites. In addition, not all the target browsers have a JSON library built in, and so there would need to be additional libraries to do the parsing. (Certainly not insurmountable, but more code to download.)

+1 for changing this, so that (among other things) Github will allow MathJax in their wikis again.

@ketch do you have a reference for Github making a statement in this regard? I don't see how this issue touches Github Wiki security.

@pkra I don't know for certain that this is the cause, but I believe it to be so. I was led to this by the comments on this page:

http://stackoverflow.com/questions/16889421/how-to-insert-javascript-to-enable-mathjax-on-github-wiki-pages

And see

https://github.com/blog/1477-content-security-policy

I believe MathJax support disappeared at the same time that they implemented the CSP feature.

Thanks, that's interesting. I'm not sure the two are related but who knows -- the github team never publicly discussed why they removed MathJax. It would be unfortunate if something that's actually a non-issue for their setup would have been the reason.

+1 for complying with CSP.

+1

I read this thread and found it very informational and fairly unbiased. I am going to stake my claim that they [github] would prevent something like exec, CSP on the matters of security and not allowing it.

Besides being mystified by that code sample and your mythic computer lore of past versions, I find it very interesting none the less and take the position of that a feature should be made available for its continued use, though it shouldn't interfere with the happy path of using MathJAX in security concerned world.

By the way, I e-mailed Github support about why they dropped MathJax. Here is the reply:

CSP was one of the reasons. Other reasons included performance issues, and hard maintenance. We might consider adding it back if we eliminated the reasons that lead us to remove it, but I can't promise that.

Thanks @ketch , that's good to know.

@dpvc should we add this to the next milestone?

:+1:

@pkra, I was planning to add it. Just hadn't gotten this far down numerically in the list yet.

@dpvc right. I was mostly wondering if fixing this would require significant changes (especially wrt inline configurations), i.e., force a version jump.

The changes we made to allow configuration via the MathJax variable should make it possible to include it without a jump. I'm pretty sure I can do it with backward compatibility and without having to make a separate "safe" version of MathJax.js. Of course, something could show up during the development that would throw a monkey wrench into the works; none of it is written or tested yet.

+1 for making MathJax secure. Too bad not being able to use this nice library in production due to the security concerns.

Any progress on this issue? I'm trying to programatically inject MathJax into pages with a Chrome extension, and I'm hitting if not a brick wall at least a decently solid wall. As described in emichael/texthings#4, my biggest problem now is with MathJax.js:265. I was able to eliminate the calls to

new Function()

pretty easily by turning them into closures as described above, but I have no clue how to get around using eval or an inline script.

EDIT: I ended up giving the user an option to add unsafe-eval and unsafe-inline to the CSP, but a long-term fix to get MathJax to a safe CSP would be nice. :+1:

@emichael, we are planning to include the changes in the next release (planned for next month), but I haven't made them yet. One reason is that I haven't looked into how you actually test this (how to set up the environment that requires it). If you could give me a suggestion of where to look or what a minimal testing environment might be, that would help.

Also, is the error about eval one that occurs at run time or at compile time? That is, does it occur if MathJax.js simply includes an eval call, even if it is never used, or does it happen only when eval actually is attempted? My reading of the spec suggests that it should be a run-time error, which would make things better for me, but I thought you might know the answer.

If you have a test server, you can just set Content-Security-Policy in the response headers. Just make sure that script-src doesn't include 'unsafe-eval' or 'unsafe-inline' (GitHub itself is a good example). If you don't, you can use a very minimal Chrome extension to inject the headers yourself.

It's a runtime error.

Thanks. I'll see what I can do.

The eval command is only used to process the in-line configuration blocks (and there is an initial one to test how global variables are handled in that case). The initial one can be put off until an inline configuration is used, and if you avoid using in-line configuration, then that should take care of it. In v2.3 we added a new way of doing in-line configuration without eval (in preparation for resolving this issue), so you can still include MathJax configuration within the HTML files without triggering the eval call.

OK, I've made a patch that removes the new Function() and eval() calls. It is based off the latest development branch, but the changes listed in f220993 should be able to be made to the master copy of MathJax.js as well, if you need to do that. You do need to allow in-line styles (there is really no getting around that). There is also a font reference to about:blank so that MathJax can test the response to a missing font (without having to make a network access), so you might want to add about: to font-src as well. Once I changed those two, this copy of MathJax ran without a hitch.

Nice! I can see why you definitely need 'unsafe-inline' for styles.

As far as the script-src 'unsafe-inline' permission goes, if I understand you correctly, scripts are only ever in-lined when the user includes inline MathJax configuration to start with. This would be fine, since everything could still work without 'unsafe-inline' in script-src. It should be mentioned in the docs, though.

Your understanding about inline scripts is correct. You do not need script-src 'unsafe-inline' unless you are using in-line configuration blocks, which you don't need to use. You can use either a local MathJax configuration file (added to the config=) or you can use a normal script file that sets the MathJax variable to an object that you would normally pass to MathJax.Hub.Config() and load that file _before_ your script that loads MathJax.js. For example, put

var MathJax = {
  tex2jax: {
    inlineMath: [['$','$'],['\\(','\\)']],
    procesEscapes: true
  }
};

into a file called mathjax-config.js and then

<script src="mathjax-config.js"></script>
<script src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_HTML"></script>

I hope that that takes care of your requirements.

It does indeed. Thanks for the quick fix!!

There are some here who would say it was not all that fast (the issue has been open for two years!), but I did have it marked for the next release, and was just getting down to it anyway, so your comment came at the appropriate time.

=> Merged.

so has anyone asked github to allow mathjax yet? it's still not possible to write a dotjs file that lets me write tex in github issues so-as to render with mathjax...

(the example here - http://stackoverflow.com/questions/16889421/how-to-insert-javascript-to-enable-mathjax-on-github-wiki-pages - fails.)

Hello,

I am currently hosting a jekyll site on github pages and following this article, and have added the following to my includes file:

<script type="text/javascript"
  src="//cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
</script>

Which is supposed to work with https or http. However, when loading my blog on a browser with https everywhere it looks messed up until you click to allow unsafe script. How can I fix this?

@silky nothing specific AFAWK. Client-side is unlikely, I think, but MathJax-node could provide a productive alternative.

@diego898 General questions fit better at http://groups.google.com/forum/#!forum/mathjax-users; always include a link to a live page, browser and OS specifics etc. Thanks.

@pkra I understand I just wasnt sure if it was bug related to this issue, or a configuration issue on my part

@diego898 no problem. What you describe does not sound like it's related to this issue.

I am still seeing this issue. See https://github.com/mathjax/MathJax/issues/1988.

@kaushalmodi, see my comment above about how to avoid the problem, or my comment to the issue you link to. You have to change how you configure MathJax if you are using a CSP that restricts script execution.

Was (2) in the original issue ever resolved? It seems that mathjax v3 still inserts