CSP: How to allow unsafe-eval for a given URI prefix (Firefox)

Mikko Rantalainen picture Mikko Rantalainen · Jun 8, 2012 · Viewed 30.8k times · Source

I'm trying to use MathJax as part of our web application which uses pretty strict Content Security Policy (CSP). The problem is that MathJax is coded to use eval() [to be exact, in form of Function()] which is not considered safe by default by CSP.

I'm using following CSP header currently:

X-Content-Security-Policy: allow 'self'; img-src *; media-src *; frame-src *; font-src *; frame-ancestors 'none'; style-src *; report-uri '/:save-csp-violation';

Which causes MathJax 2.0 code to fail because it uses Function(). I tried to allow unsafe-eval (i.e. Function()) only for MathJax located within the same origin below path /:static/math/. To do that, I tried to add

unsafe-eval '/:static/math/*'

to make the full header look like

X-Content-Security-Policy: allow 'self'; img-src *; media-src *; frame-src *; font-src *; frame-ancestors 'none'; style-src *; report-uri '/:save-csp-violation'; unsafe-eval '/:static/math/*'

but I still cannot Firefox 13.0 to run the code. I'm getting an error message to Firefox Web Console (located in Tools - Web Developer):

[10:09:59.072] call to Function() blocked by CSP @ http://localhost:8080/:static/math/2.0/MathJax.js?config=TeX-AMS-MML_HTMLorMML:29

However, I'm not getting a CSP report to the 'report-uri'. (As you see, I'm currently running the test through custom localhost port without SSL, in case that makes a difference. The colon before static is not a typo, I'm reserving all path parts starting with a colon for internal use of the application, all user content may freely define other URLs.)

Is my use of unsafe-eval attribute incorrect or is it impossible to allow unsafe-eval only for subset of 'self'? The intent is to allow unsafe-eval only for same origin path prefix /:static/math, strict CSP JS code execution for 'self' and no JS code for any other method.

Answer

Mikko Rantalainen picture Mikko Rantalainen · Jun 8, 2012

There're multiple issues:

  1. The Content-Security-Policy (CSP) header does not work this way. CSP only has granularity of a single host+port combination (origin). If you cannot allow all scripts to have unsafe-eval, no script can have it. The only possible workaround is to not use a script that requires unsafe-eval (fortunately, MathJax no longer requires unsafe-eval since MathJax bug 256 was fixed).

  2. The allow syntax is an old Mozilla variant and should not be used. The current syntax is to say default-src followed by scheme or host names or origins that are allowed as source of everything and then override the default value for each sub type (e.g. script-src) as needed. Some sources may support additional source keywords in addition to self. For example, the script-src supports unsafe-eval which means that any script that is otherwise allowed to execute is allowed to run eval() or Function(), and unsafe-inline meaning that any piece of markup that can support some kind of inline script is allowed to execute. Allowing unsafe-eval may be acceptable but unsafe-inline is pretty much no-go with script-src (otherwise, you should not bother with the CSP at all).

  3. The correct syntax for script-src as follows:

    script-src 'self' cdnjs.cloudflare.com
    

    combined with loading MathJax from https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.4/MathJax.js

  4. MathJax also uses inline style attributes so following is needed (unless already allowed) or MathJax will raise Exception while trying to render the math:

    style-src 'self' 'unsafe-inline'
    

    It is not possible to use CSP to allow JS to insert style attributes and not have style attributes already inserted in the HTML source to have an effect.

  5. It seems that Firefox 13.0 (at least) does not immediately "call home" in case of CSP violation. Most of the violation reports do get submitted some time after the event. Chrome seems to be much more aggressive with the report submission which will make it a bit easier to test. From my experience, Firefox does not always send CSP report at all - it may be using some kind of heuristic to not send repeated messages.

In the end, to make MathJax work with Content-Security-Protection, you need following headers (assuming you're using MathJax via CDNJS):

Content-Security-Policy: default-src 'self'; script-src 'self' cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline';

Older browsers (e.g. Firefox 13) used to require extra parameters such as options or required using non-standard headere name such as X-Content-Security-Policy or X-WebKit-CSP. These hacks are no longer required because user agents support standard header nowadays. (With the exception of MSIE in contrary to MS Edge.)