Like any kind of apps, JavaScript apps also have to be written well.
Otherwise, we run into all kinds of issues later on.
In this article, we’ll look at some best practices we should follow when writing Node apps.
Support Blacklisting JWTs
We should be able to blacklist JSON web tokens so that we can lock out malicious users,
There are no mechanisms to do this for most systems.
We can add a list of untrusted tokens to prevent them from logging in.
Prevent Brute-Force Attacks Against Authorization
Brute-force attacks against authorization can be prevented with rate limiting.
For instance, we can limit the login attempts by the block repeated failed login requests.
Run Node.js as a Non-root User
Non-root user should be used to run Node apps.
This way, they can do whatever they want in our system.
We can bake that into the Docker image or set it with the -u
flag.
Limit Payload Size Using a Reverse Proxy or a Middleware
Payload size should be limited to avoid overloading our systems.
This can help with preventing DOS attacks.
If the requests’ body size is small, less damage can be done.
We can set express body parser to accept small size payloads with the limit
option.
Avoid JavaScript eval Statements
We can avoid JavaScript eval
statements.
They’re insecure since code is run from a string.
It also makes optimizations and debugging impossible.
setTimeout
, setInterval
, and the Function
constructor also run code from strings.
So we should avoid passing strings to them as well.
Prevent Evil RegEx from Overloading Single Thread Execution
There’s some regex that we should avoid.
To make data validation easy, we can use a library like validator.js or look up safe regex we can use with safe-regex to detect vulnerable regex patterns to avoid.
Bad regex can make our app susceptible to DOS attacks that block the event loop.
This will make our app hang.
Avoid Module Loading Using a Variable
We shouldn’t call require
with a variable.
This way, we can’t let attackers pass anything into the require
function.
For instance, instead of writing:
const insecure = require(helperPath);
We write:
const uploadHelpers = require('./helpers/upload');
This also applies to other paths we pass in like when we read a file with fs.readFile
.
Run Unsafe Code in a Sandbox
If we have any unsafe code, we should run them in a sandbox.
This way, they can’t get to the outside world and potentially do damage.
NPM packages can be sandboxed. A dedicated process can also be sandboxed.
Take Extra Care When Working with Child Processes
If we run child processes in our Node app, we should sanitize the command string so that we can run without risks.
If we don’t escape them, then attackers can run anything they want, which can be catastrophic.
Hide Error Details from Clients
If there are any details about errors that expose the internals of our system, we should hide them from clients.
This way, the chance of attackers finding ways to attack our app is much lower.
Anything like paths, stack traces, and more should be hidden.
Conclusion
We should hide sensitive data, isolate risky code, and escape any strings that are potentially malicious.