How to waive security issues in your JavaScript frontend project
Tags: javascript, webdev, security, npm
Published: 2023-11-23 20:41:20

We've all been there and we all know it: [npm audit is broken by design](https://overreacted.io/npm-audit-broken-by-design/). And it's not just npm, all security scanners I have come across has the same flaws as npm audit, which seems to boil down to one issue: Security audit tools doesn't know the difference between:

1. JavaScript running in the browser.
2. JavaScript running on the server.
3. JavaScript running during development.

---


Knowing about these 3 different scenarios, and which security issues are a threat in which scenarios, is crucial to become good at identifying security issues that doesn't affect your project. Whenever you are working through a security report, you should do the following:

1. Identify the attack vector of the given security vulnerability.
2. Identify where the package with the security issue are being used.
3. Conclude if the security issue has an attack vector in the scenario it is used.

Lets have a look at each attack vector by itself.

# 1. The browser (client side)

The browser process HTML, JavaScript and CSS in order to provide a GUI. Doing this, it can be exposed to a multitude of vulnerabilities:

## Cross-Site Scripting (XSS) ([OWASP](https://owasp.org/www-community/attacks/xss/))

Malicious scripts are injected into otherwise benign and trusted websites. This can be done by malicious end-users who insert JavaScript into text input-fields, or hijacks requests from the browser to the backend, and inserts JavaScript into the data.

Specifically, what happens in an XSS vulnerability, is that when data, containing JavaScript, is displayed in the browser, the browser executes the script.

Can XSS be an attack vector when running on the server? If your application does "server side rendering", it actually might! But this is not the same as "Server XSS" explained in [documents like this](https://owasp.org/www-community/Types_of_Cross-Site_Scripting), which means that the exploit is stored on the server, but executed in the browser. The sanest way of dealing with XSS is still to make sure that the malicious payload isn't executed.

Can XSS be an attack vector during development? Not really. Even though the XSS vulnerability itself is present in the browser during development, and test runs, there aren't any third party who can inject malicious scripts into the test data (unless it comes from a compromised dependency, as explained below).

## Cross-Site Request Forgery (CSRF) ([OWASP](https://owasp.org/www-community/attacks/csrf))

A script performs an action on the site, as if it is performed by the currently authenticated user. For instance, to perform payments in a web store, change email or password on a profile settings page, etc.

CSRF is not an attack vector on the server or during development. But, like an XSS, CSRF attacks can be stored on the server, waiting to be executed (like an `iframe` or `img` code-snippet inserted into a input field).

# 2. On the server

The server process requests from the clients, and hands the client data, web pages and media resources. Vulnerabilities on the server side includes things like

## Regular Expression Denial of Service (ReDoS) ([OWASP](https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS))

Denial of Service attacks consists of overloading the server with work, so it becomes unresponsive. a ReDoS vulnerability is a special kind of Regular Expressions that can be exploited by crafted strings that the server spends a lot of time and resources parsing.

ReDoS is not a vulnerability in the browser, nor during development. Regexes vulnerable to ReDos located in code running in the browser _might_ become a nuisance for the end-user, if the end-user is unlucky enough to provide an ill formed string as user-input (albeit this is highly unlikely to happen). But this is hardly a security vulnerability.

## Server Side Request Forgery (SSRF) ([OWASP](https://owasp.org/www-community/attacks/Server_Side_Request_Forgery))

Tricks the server into making unintended requests within internal resources. This happens typically when the server imports or use data from an URL that can be tampered with (including the request URL itself). The attacker can exploit this simply by editing URLs.

This is not a vulnerability in browsers nor during development.

# 3. During development

Also during development, there are a lot of JavaScript running. We run all browser JavaScript on our own browser during development, as well as the server JavaScript. In addition to this, we also run all code typically located in `devDependencies` in package.json. This is code for building the application, testing the application and other utilities used during development.

It is also worth remembering that its not only developers who run this code, but also continuous integrations services such as Jenkins. So this code isn't only run on our personal computers, but also on the company's infrastructure itself.

Vulnerabilities that uses this attack vector includes

## Malware ([OWASP](https://owasp.org/www-chapter-singapore/assets/presos/Supply_Chain_Security_Securing_your_NPM,_PyPI,_Maven_and_Crates_(Rust),_Shift_Left_with_Gitops_and_Software_Fuzzing.pdf))

npm packages that are straight out malware, is definitely the worst kind of security vulnerability your code base can consist of. In my honest opinion, this should have its own flag, or grade of seriousness _above_ all other vulnerabilities. Unfortunately, it is deemed just as dangerous as ReDoS vulnerabilities by most security scanners. `"¯\_(ツ)_/¯"`

Malware is, of course, also a direct threat to code running on the server or the browser. But the damage is already done if it manages to run on a developer machine or during CI.

## Compromised dependencies

Pretty much the same as malware. It is worth considering, though, that server side vulnerabilities, that under normal circumstances won't be a threat during development, can be exploited by compromised dependencies and serve as vulnerabilities also during development.

For instance, a ReDoS needs a crafted string input to be a vulnerability, and we usually have full control of all input string during development. But if we download a compromised npm package, that package can exploit the ReDoS to stagger development resources (including the developers itself figuring out what's going on).

# Inspecting individual vulnerabilities and decide if your application is afflicted

By dividing the world into three target vectors (browser, server and development platform), and identifying what vector the different vulnerabilities affects, we have a powerful tool that lets us "waive" a lot of vulnerabilities:

* All ReDoS vulnerabilities in code that only runs in the browser or during development, can be waived.
* All XSS vulnerabilities that only runs on the server or during development, can be waived.
* All CSRF and SSRF vulnerabilities found in code only run during development or at build time, can be waived.

And so forth. In my experience, a lot of the "severe" vulnerabilities in security logs are there rather innocent ReDoS-vulnerabilities that either runs outside of the server, or runs in a context where we have full control of their input either way. (It looks like the ones making the security reports disregard the context of the Regular Expression in its entirety.)

But how do you know where a dependency is running? `npm ls` to the rescue! Say that your report claims there is a ReDoS vulnerability in the package "wrap-ansi" (made up example, I apologize to the creatores of wrap-ansi in advance). What you can do is run `npm ls wrap-ansi` within your project:


```bash
13:49 $ npm ls wrap-ansi
application@1.2.3 /home/username/code/application
├─┬ puppeteer@21.5.1
│ └─┬ @puppeteer/browsers@1.8.0
│   └─┬ yargs@17.7.2
│     └─┬ cliui@8.0.1
│       └── wrap-ansi@7.0.0 deduped
├─┬ react-scripts@5.0.1
│ └─┬ jest@27.5.1
│   └─┬ jest-cli@27.5.1
│     └─┬ yargs@16.2.0
│       └─┬ cliui@7.0.4
│         └── wrap-ansi@7.0.0 deduped
├─┬ rimraf@5.0.5
│ └─┬ glob@10.3.10
│   └─┬ jackspeak@2.3.6
│     └─┬ @isaacs/cliui@8.0.2
│       └── wrap-ansi@8.1.0
└─┬ source-map-explorer@2.5.3
  └─┬ yargs@16.2.0
    └─┬ cliui@7.0.4
      └── wrap-ansi@7.0.0
```

We see that the four ocurences of wrap-ansi is used through

* puppeteer, used for testing
* react-scripts, used for development
* rimraf, used for development and building
* source-map-explorer, used during development

So we can safely waive/ignore any ReDoS reports on `wrap-ansi` for this application. **BUT**...

Some other dependency, used on the server side, might include a dependency on `wrap-ansi` in the future. So we cannot be sure that this (make belief) ReDoS vulnerability in this version on `wrap-ansi` will be benign for us in all eternity. So the waiver should have a sensible duration!

Also, it is worth mentioning, dependencies should be kept as updated as we can, at all times, either way!