JS/ DOM events

Table of Contents

    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 (within this post, 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 for 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

    WebAssembly

    An alternative approach is WebAssembly, an(other) W3C standard. It adds (by downloading) a high performance runtime environment to the browser, that executes component (assembly)-based WebAssembly (WASM) code, precompiled from languages like C(++), C#, Kotlin, or Rust. WebAssembly components can also be executed in the back-end, allowing for flexible rendering load. As precompiled components are less flexible than interpreted JavaScript code, both can be combined and exchange data via a WebAssembly JavaScript API.

    … and a strict CSP

    Regarding CSP, WebAssembly is not “an alternative approach”, but faces the very same issue described above, as it cannot directly modify the DOM, but needs to involve JavaScript to do so. But at least, it can do so, as it runs locally within the browser. But worse, a strict CSP does not even allow WebAssembly to run. It requires an “unsafe-eval” exception, which makes the CSP no longer strict. So for my use case, currently, an AJAX-based JSON-RPC interface is the way to go.

    A strange perspective?

    Famous software development advocate and speaker Martin Fowler has implemented his own web site plainly using static content (compiled using Ruby), because, as he tells in one of his talks,

    “‘[…] JavaScript is a complete mess.”

    Referring to the first paragraph above, maybe it’s not the programming language as such, but its combination with the browser environment (Browser Object Model), and the DOM.

    Comments are closed.