Categories
Node.js Best Practices

Node.js Best Practices — Improving Security

Spread the love

Node.js is a popular runtime to write apps for. These apps are often production quality apps that are used by many people. To make maintaining them easier, we’ve to set some guidelines for people to follow.

In this article, we’ll look at how to improve the security of our Node.js apps.

Prevent Brute-Force Attacks Against Authorization

We should rate limit our login routes so that attackers can’t have too many failed login attempts in a period of time. To do this, we can check the IP address or the ID or name of the computer that’s trying to log in.

Otherwise, attackers may make enough guesses to make the correct guess to log in.

To add brute force protection to an Express app, we can use the express-brute package. We can use it as follows:

const express = require('express');
const bodyParser = require('body-parser');
const ExpressBrute = require('express-brute');
const store = new ExpressBrute.MemoryStore();
const bruteforce = new ExpressBrute(store);

const app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

app.post('/auth',
  bruteforce.prevent,
  (req, res, next) => {
    res.send('Success!');
  }
);

app.listen(3000, () => console.log('server started'));

Then when an attack hits the route too often, then the attacker will get a 429 response indicating the route has been hit too many times from the same origin.

Run Node.js as Non-Root User

Running a Node.js app as a root user is dangerous. It can do anything to the computer, which is probably not what we want. To reduce the risks from running our Node app, we should create a non-root user and bake it into our Docker image.

Then we can add the -u username option to run the Docker container with the specified user. Replace username with the username of your user.

Attackers then can’t run anything on our system.

Limit Payload Size Using a Reverse-Proxy or a Middleware

We can limit the payload size of requests by using reverse proxies or middleware to control request payload sizes. For instance, we can set the limit option of the Express body-parser middleware to limit the payload size as follows:

const express = require('express');
const bodyParser = require('body-parser');

const app = express();

app.use(bodyParser.json({ limit: '200kb' }));
app.use(bodyParser.urlencoded({ extended: true }));

app.post('/', (req, res, next) => {
  res.send('hello');
});

app.listen(3000, () => console.log('server started'));

We just set the limit value to '200kb' to limit the request payload size to 200KB.

Avoid JavaScript Eval Statements

We should never use the eval function in any code. This is because it lets attackers run code that’s injected into eval . Also, the performance from running code from a string is slow.

Another similar construct that should be avoided is the new Function constructor. It takes one or more string arguments for parameter names and code to run. Like eval , it lets people run code from a string, which means people can run anything with strings.

setTimeout and setInterval should also never behave dynamic JavaScript code passed in as arguments.

If we eliminate these constructs, we eliminate lots of risks for cross-site scripting attacks as attackers can’t inject malicious via these constructs.

Prevent RegExp From Overloading Single Thread Execution

Regular expression processing in JavaScript is inefficient. A single request that validates a few words can block the entire event loop for a few seconds.

Therefore, we should use 3rd party validation packages like validator.js instead of writing our own regex patterns. Also, we should make use of safe-regex to detect vulnerable regex patterns.

Avoid Module Loading Using a Variable

Module loading should never be done with variables. This allows attackers to potentially inject malicious code to import malicious code into our app and run it.

This also applies to anything that’s for accessing files like reading files. Eslint-plugin-security can detect this and stop you from committing such changes.

Run Unsafe Code in a Sandbox

Sandbox execution environments are great for testing out code that’s potentially unsafe. We can use something like cluster.fork() to create a new sandboxed environment to run any potentially bad code.

Conclusion

When we run Node apps, we should think about potential security issues when writing apps. Never should we let users run code dynamically. This means we should avoid using constructs like eval , dynamic imports, Function constructor, and other things like that.

To prevent brute-force attacks, we should rate limit our authentication API endpoints to avoid brute-force attacks. In JavaScript, regex can block the event loop for a long time. Therefore, we let others create our regex and validate it by using 3rd-party libraries.

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

Your email address will not be published. Required fields are marked *