Javascript supply chain attack scenarios and mitigation methods

There are a few ways to include Javascript code into a web site. Depending on who writes the code and where it is hosted, there are different supply chain security considerations and mitigation methods. I will briefly cover each case and discuss what I think are gaps in current solutions.

Third-party libraries

At the top (or leftmost) of the code lifecycle there are likely third-party libraries that are included as dependencies, such as ones from NPM. One way to ensure consistency is to follow available versioning data (e.g. semver), and consciously update these third-party libraries at a reasonable frequency. Once a new set of versions is picked, you can run them through vulnerability management tools, static and dynamic code analysis tools, and testing.

It is a harder problem to verify that NPM itself has not been compromised to host malicious library code. One way to detect against this attack vector is to fetch the same set of libraries at fixed version sets from multiple vantage points on the Internet, or temporally over a period of time. However it does not completely solve the issue. I am not aware of a standard solution, or heard of people actively monitoring for this attack scenario.

Including Javascript Files

Javascript files are mostly hosted as static assets, either on the same machine as the web server, or more commonly in CDNs. Sometimes, you are also including other people's Javascript files directly from them, such as Google Analytics. In the context of Single Origin Policy, these included files are now acting in the parent origin. Regardless of where the file is hosted, there are two methods to declare intent.

The first is Content Security Policy (CSP), which is a HTTP header sent on a per-page basis. CSP can do quite a few things, but in this context it can declare a list of domains allowed to be included in the page being served. Any Javascript files outside of these domains would be blocked by the browser. However, the level of granularity stops at the domain level.

The second is Subresource Integrity (SRI), which is a per-include (<script>) pre-computed hash that indicates the expected file in the include. This is a fairly fine grained capability to ensure consistency, especially if the included file is versioned. This does not work if the owner of the included file is not aware of this restriction and intends to update their files without versioning at the same path, or if the included Javascript file further includes other files which can change without reflecting in the hash.

One immediate improvement to SRI would be to allow hashes of all recursive dependencies, which is mostly straightforward to generate (unless there are dynamic construction of URLs). It would be even better if such external Javascript files all move to a versioned scheme found in libraries and software, so better consistency can be achieved.

There are use cases where an included Javascript changes each time it is served, or are dynamically generated depending on the environment. In these cases, if SRI were to be used, these external parties need to have an out-of-band channel to dynamically generate and deliver SRI hashes so they can be included when including these external files. This creates engineering and timing problems, and still does not solve the problem of a compromised external server that is both serving malicious Javascript files and computing its hash.

An even more ambitious approach is to template these external Javascript files, and separate code from data. This way, the Javascript files become less dynamic but with pre-defined variables where dynamic data can be populated. This is not dissimilar to prepared statements in SQL to prevent SQL injection attacks. The data component can be fetched without SRI and fed into templated Javascript files which are served with SRI. This approach further increases SRI coverage.

Inline Javascript Code

Inline Javascript code are not recommended these days because blocking all inline scripts systematically prevents XSS attacks. However, if inline code is really needed, CSP also offers declaration of intent with either a pre-computed hash or a random nonce in the CSP HTTP header.

User-generated Javascript

This is a case where a website is intentionally allowing users to input Javascript for a later presentation either to the same user or to a different consumer, most likely through an eval(). I do not think there is a general solution that can allow this securely. In the few instances where I have seen this happening, blacklist or whitelist approaches need to be used to reduce to a subset of all Javascript to ensure security. Of course, there are many Javascript obfuscation methods that make blacklisting or whitelisting difficult to get right.


I am still fairly new to some of these capabilities, so please let me know if there are errors in this post.