The Hidden Risks of unsafe-eval
Content Security Policy (CSP) deployment is rarely a clean cut. After removing unsafe-inline from policies at Dropbox via nonce-based sources, the next major hurdle was the persistent presence of unsafe-eval. While legacy JavaScript templates necessitated this directive, a deeper investigation revealed that allowing it introduces vector-specific XSS risks that are easy to underestimate, particularly when using popular client-side libraries.
At face value, unsafe-eval appears to be a low-risk directive. An attacker who can already invoke eval or new Function has essentially achieved code execution. The threat model assumes that an injection point must flow into an eval "sink" for the directive to be abused, unlike unsafe-inline, which turns basic HTML injection into code execution directly. Unfortunately, this logical reasoning breaks down when libraries such as jQuery or Prototype are in play.
How jQuery Circumvents Inline Script Restrictions
Consider the difference between standard DOM APIs and jQuery's methods. Setting an element's innerHTML property with untrusted input will block inline <script> tags from executing. However, using a library method like .html() changes the game entirely.
When jQuery's domManip function processes a string containing a <script> tag, it does not rely solely on innerHTML. Realizing the browser will not execute the script natively, jQuery parses out the script contents and directly evaluates the code via eval. This means an HTML injection vulnerability becomes a code execution vulnerability even with a strict CSP lacking unsafe-inline, solely because unsafe-eval is permitted. Furthermore, if the untrusted input is a URL, jQuery will fetch that external resource and evaluate the response, completely bypassing the CSP's host whitelist for scripts.
Similar "unexpected eval" behavior exists in the jQuery.ajax function. By design, if the response to an XHR request has a content-type of script, jQuery automatically executes the response body via eval. This transforms any attacker-controlled AJAX target URL into a potential code injection point.
Patching Unexpected Evals
Removing unsafe-eval was not a quick option due to legacy dependencies. Instead, Dropbox implemented security patches to block these specific unsafe behaviors at the library level. These jQuery patches target two critical areas.
The first patch removes the implicit eval in AJAX script responses. This is done by replacing jQuery's default handler for script responses—which is typically set to execute the response—with a no-op function:
jQuery.ajaxSettings.converters["text script"] = true
The second patch overrides the default domManip function. While the patch mostly mirrors the original jQuery implementation verbatim, the critical modification lies at the core of the function. It enforces a check on any parsed <script> tag, validating the presence of a correct nonce value before allowing the code to execute:
// line 181:
for (i = 0; i < hasScripts; i++) {
node = scripts[i];
if ((window.CSP_SCRIPT_NONCE != null) &&
(window.CSP_SCRIPT_NONCE !== node.getAttribute('nonce')) {
console.error("Refused to execute script because CSP_SCRIPT_NONCE" +
" is defined and the nonce doesn't match.");
continue;
}
An alternative to this specific patching is to entirely disable the behavior by deleting these code paths or utilizing sanitization libraries like jPurify for all DOM operations. Regardless of the method, addressing these eval-adjacent risks is essential if a CSP policy retains unsafe-eval.
Securing Legitimate Template Evaluations
Legacy usage of JavaScript micro-templating is a common reason unsafe-eval cannot be dropped immediately. These libraries typically retrieve template content from <script> tags with a custom content-type (e.g., text/template) and compile it using new Function.
<script type="text/html" id="user_tmpl">
<% for ( var i = 0; i < users.length; i++ ) { %>
<li><a href="<%=users[i].url%>"><%=users[i].name%></a></li>
<% } %>
</script>
This architecture exposes a significant flaw: an attacker with an HTML injection vector can plant their own malicious template inside such a tag, which the library will subsequently evaluate. To close this hole, Dropbox applied nonce attributes to all legitimate template script tags. The template library was then modified to verify these nonce values before compilation, mirroring the browser's native nonce checking for traditional script nodes.
<script id=test type=text/template nonce=1234>
...// template library only processes this if
...// window.CSP_SCRIPT_NONCE equals 1234
</script>
<script type=text/template>
...//the templating library will ignore this
</script>
Handling dynamically downloaded templates introduced a nonce validation problem, as server-side nonces are generated per page load. If a template was fetched after the initial page render, its nonce would not match the page's current nonce. The workaround involved changing the server-side nonce generation logic. Instead of a per-request random value, the script nonce is now derived as a hash of the CSRF token for the session. Since CSRF tokens are already random and unguessable, this maintains the nonce's integrity while allowing it to remain stable and valid for subsequent requests within the same user session.
These mitigations are critical, but they highlight a broader security stance. CSP functions as a defense-in-depth measure, not a first line of defense. The primary protection against XSS remains building the DOM securely via frameworks that auto-escape untrusted data, reinforced by robust DOM sanitization as a follow-up fail-safe.
![[CSP] The Unexpected Eval](/covers/ad7421a102.webp?v=8564867)


