CSP: How to allow unsafe-eval for a given URI prefix (Firefox)
There're multiple issues:
-
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 haveunsafe-eval
, no script can have it. The only possible workaround is to not use a script that requiresunsafe-eval
(fortunately, MathJax no longer requiresunsafe-eval
since MathJax bug 256 was fixed). -
The
allow
syntax is an old Mozilla variant and should not be used. The current syntax is to saydefault-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 toself
. For example, thescript-src
supportsunsafe-eval
which means that any script that is otherwise allowed to execute is allowed to run eval() or Function(), andunsafe-inline
meaning that any piece of markup that can support some kind of inline script is allowed to execute. Allowingunsafe-eval
may be acceptable butunsafe-inline
is pretty much no-go with script-src (otherwise, you should not bother with the CSP at all). -
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
-
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.
-
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.)
Year 2021 Update:
CSP version 2 allows specifying paths in origins, too. However, be warned that using paths is a breaking change where backwards compatibility is a bit unknown. The problematic part is that server needs to emit CSP header before it knows if user agent supports CSP1 or CSP2.
Related videos on Youtube
Mikko Rantalainen
My daily work is a closed source PHP project but I'm really interested in open source projects and I know PHP, C/C++, JavaScript and Perl 5 pretty well. I can do some Java, Python, x86 assembler (intel syntax) and some other programming languages, too. Purely functional languages such as Haskell are still a bit hard for me. I can do some linux kernel programming, too. I'm currently running Ubuntu (workstation, home computer, laptop) and LineageOS (phone) as my OS of choice. GPG: 563168EB
Updated on June 16, 2021Comments
-
Mikko Rantalainen almost 3 years
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 ofFunction()
] 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 addunsafe-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.-
InterLinked over 3 yearsYou can do this. Unset the header and then set it again using a <Directory> command.
-
Mikko Rantalainen over 3 years@InterLinked: could you provide a link to documentation?
-
InterLinked over 3 years
-
InterLinked over 3 yearse.g. say you have phpMyAdmin -
<Directory "/var/www/html/pma/"> Header unset Content-Security-Policy Header unset Content-Security-Policy-Report-Only Header set Content-Security-Policy "base-uri 'none'; frame-ancestors 'self'; object-src 'self'; script-src 'self' 'unsafe-inline';" </Directory>
-
Mikko Rantalainen over 3 years@InterLinked It seems that you have misunderstood the question. The question is not about how to send different CSP header for different URLs but how to declare that a given page is not allowed to execute scripts located outside specific URL prefix. The CSP header is about asking the browser to do the limitation and as such, the server settings for other URLs (be it Apache or Node.js or something else) cannot help unless the browser co-operates.
-
InterLinked over 3 yearsThe OP asked "how to allow XYZ for a given URL prefix". The only way that I've found is to unset the CSP and apply a different CSP. That's what I do when I need to allow something like
unsafe-inline
(usually for something 3rd party I didn't write). It's not as elegant but it does do the job. Now with regard to limiting scripts to a certain directory, that may be something else entirely. So maybe not useful here after all but could be if you were trying to do something slightly different - perhaps put those scripts on a subdomain and allow that subdomain? -
Mikko Rantalainen over 3 yearsYeah, I know because I'm the OP. However, say you have a page at
https://example.com/page1
and you want to display MathJax enabled content on that page but not allow other JavaScript files used by the same page but those used by MathJax to useunsafe-inline
. Unless browser co-operates, you cannot do that. And Apache<Directory>
setting on the server will not definitely do that.
-
-
Mikko Rantalainen almost 12 yearsFirefox 13.0 also contains a bug or missing feature that it's CSP header does not match the W3C variant. See the bug here: bugzilla.mozilla.org/show_bug.cgi?id=746978
-
vhs about 5 years"If you cannot allow all scripts to have unsafe-eval, no script can have it." Not true, if you use the
meta
tag and vary the policy based on page content. -
Mikko Rantalainen about 5 years@JoshHabdas I was speaking about a single page having multiple
<script>
elements. You cannot specify that one<script>
is able to run unsafe-eval and another script cannot do the same thing. Of course, for each HTML page one can send different HTTP headers so there's no need for<meta>
tag if you can really control the server. -
vhs about 5 yearsAh, okay. Sorry I misunderstood! Please flag for redaction :) Re: controlling the server that's nice and all but I like to control as much as possible with semantics to keep all the logic in one place.
-
Mikko Rantalainen about 5 yearsI think it's okay to keep all the comments here. Those may help future readers to understand the situation. I also like to keep everything in one place but my programming framework allows emitting any HTTP headers in addition to the normal content. HTTP headers are pretty much always a better choice over
<meta>
elements if you have a choice. -
vhs about 5 yearsMakes sense, Mikko. Hey, could you share with me what you're using to emit the headers? I'm using Hugo and could create a custom output format to spit out the headers as a kind of MIME type for use by a web server but I haven't come across the right tool for that job yet. Thanks in advance!
-
Mikko Rantalainen about 5 years@JoshHabdas: I write PHP for my daily job as listed in my profile description. There one simply uses function
header()
instead ofprint()
(and to be exact,print()
is a language construct in PHP, not a function) to emit any custom HTTP headers.