JavaScript events

As mentioned on the JavaScript page, the combination (should we really call it an “integration”?) of HTML, CSS, and JavaScript, from a security point of view, is a worst case. Dynamic styling is provided by CSS (3+), but for dynamic behavior of web pages, JavaScript (including TypeScript) is needed. And it seems, that this is a multi-lane, but one-way road. Multi-lane, because there is a plethora of (mostly huge) JavaScript libraries (e.g. JQuery) and frameworks (e.g. React); one-way, because once having started to use such tool, there is virtually no way back unless you develop huge amounts of JavaScript code on your own, which seems like “reinventing the wheel”.

JavaScript included in HTML

One possible reason for these huge client frameworks I stumbled on when introducing nonces as part of a Content Security Policy (CSP) version 2. But before diving into this, we first need a basic understanding of how to include JavaScript into HTML. There are currently three options to do that.

  1. Inline events as part of the HTML element definition (e.g. onclick, onkeyup). Inline events are considered insecure, which is why a strict CSP will not execute the same (it requires the CSP exception “unsafe-inline“, which cancels its security, which is why it might better be called “insecure-inline”).
  2. JavaScript code surrounded by <script> tags anywhere on (preferably at the end of) your web page. In order to trust such script blocks, a hash or nonce that was generated by the trusted back-end, is required.
  3. JavaScript code loaded from files (<script> tags containing the “src” attribute). Regarding trust, the same as for 2. applies.

All three ways may be used to register JavaScript events to HTML elements, but the first one is insecure, so apart from other advantages like registering multiple actions per event, trusted scripts of type 2. and 3., using the “addEventlListener()” method of an HTML element, are a better choice.

Asynchronous JavaScript and XML (AJAX) embedded in HTML

Dynamic behavior, the moment it needs data from the back-end, requires AJAX calls (HTTP requests triggered via JavaScript). An AJAX call is very generic, as the response may contain whatever data the back-end is instructed to generate, not just retrieving records from a database. So it may also return new HTML elements that haven’t existed on the page so far. The simplest way to achieve that is to use the “innerHTML” property of an HTML element addressed by the AJAX call. The response will then be inserted (as is) as content of that element. In that context, “insert” may also be referred to as “inject“. This has been my favorite approach to enable dynamic behavior with a minimum amount of JavaScript code.

Side-note: only use “innerHTML” for trusted back-end HTML. For any other source of strings, use “innerText“, as this tells the browser to not interpret the same.

The deadlock

Trouble arises when dynamic behavior using AJAX is to work with a strict CSP, as it trusts scripts by the HTTP header of the page. Any other JavaScript code (inline, inserted, or altered) is blocked. In other words: trusted JavaScript code is immutable. Imagine the AJAX response contains new HTML elements, which should have JavaScript listeners registered to some of their events. This is only possible using option 1., the (unsafe) inline events, which are insecure. Why?

Remember the terms “innerHTML” and “inject”?

  • “innerHTML” is what it is supposed to be: HTML, which is not evaluated by JavaScript. We would have to force the evaluation of the script part of the content by using the eval() function, which of course is never an option, as it is completely insecure, and thus blocked by a strict CSP. When using option 1, the unsafe inline event listeners, the HTML interpreter does the job of registering the event listeners.
  • The new HTML elements are “injected” by the back-end, and added to the Document Object Model (DOM), but without the ability to run trusted JavaScript code, there is no way to register event listeners using options 2. or 3.. Trying to pre-register known HTML elements that are to be injected by means of AJAX requests, won’t work, as JavaScript will only register event listeners to objects that exist the moment the listener is to be added.

A potential consequence

There seems to be no promising approach to tackle that deadlock, but obviously a logical one is to have more trusted JavaScript code replacing the “injection” of new HTML <script> elements. Processing newly added or updated HTML elements using trusted JavaScript code enables to also register event listeners to the same. And exactly this might be one reason of the one-way road mentioned above. Seems like an “ever more” addiction.

How to

The key element to bridge the AJAX response to the trusted JavaScript code is a hook within the requester code. The requester checks the JSON data for a key word to a trusted JavaScript function before sending the data to the back-end. Subsequently, the requester waits for the response to complete (remember that JavaScript is asynchronous), and then invokes the post-processing function corresponding to the key word (do not evaluate function strings!). As the post-processing function is contained in the JavaScript code trusted by the page header, that contains the strict CSP, it is not blocked by altering the DOM. Nevertheless, this means, that any post-processing function (which is, per use case or context) needs to be implemented in JavaScript libraries. So why implement them twice (front-end as well as back-end)? And voila, we end up with a client framework.
The following diagram depicts these flows, first the initial loading of the page setting the strict CSP, then updates to elements within it using AJAX. Text highlighted green indicates positive aspects from a security point of view, but not necessarily from a developer point of view.

CSP initial load
CSP AJAX updates

Comments are closed.