Node.js Security: Leveraging the Best Practices for Our Web Apps
Market usage statistics portray that Node.js has become one of the primary frameworks for web application development. For instance, Netflix adopted Node.js with the plan to expand its application’s capacity to serve more than 200 million subscribers. The interesting part of the story is that Netflix leveraged Node.js framework to improve its performance and, most importantly, upgrade application security.
The exponential user growth demands rigorous security coverage. It was Guilherme Hermeto, the Senior Platform Engineer at Netflix, who made the efforts to restructure Node.js infrastructure for the application. The goal of employing Nodejs was to improve its debugging capabilities with a wide range of diagnostic tools and increase layers of security for the application.
From our experience with Node.js, we’ve realized that popularity and growth come along with certain risks. Security vulnerabilities are not new for open-source backend frameworks. Every Node.js developer understands the risks that hackers can pose for their application and user data. Keeping Node.js security risks in mind, the article would focus on the best practices and tools development teams can utilize for improving their web application security.
Best Practices for Improving Node.js Security
I have had experiences where clients personally asked if a particular backend framework, like Node.js, is secure or not. I would not laugh at this innocent question nor doubt their naivety since every web application can be hacked or exploited with the right resources. While the risks are difficult to anticipate, it is always preferable to follow the best practices.
For ease of understanding, I have categorized the top 10 Node.js security practices that center around application security, data security, server security, and platform security.
1. Keep an eye on logging and monitoring to avoid irregularities
Irregular logging and monitoring can cause several security vulnerabilities that can cost the company a fortune. Most CTOs recommend frequent penetration tests that can help in detecting irregularities instead of waiting for an incident to be reported.
The French game developing company, Voodoo, provides an exemplary example of regular monitoring and logging activities. Memory leaks usually become a threat for an application that regularly receives large amounts of inputs from its users. The development team at Voodoo used Node.js API ‘Inspector’ to collect metrics and real-time data of the application. By regularly monitoring KPIs like CPU consumption, they were able to avoid memory leaks.
Recommended modules to use for logging and monitoring activities:
- Node.js internal modules like Winston, Bunyan, and Pinto allow streaming. These can be used to handle uncaught exceptions and querying logs.
- The logs could also be used for feeding Intrusion Detection or Intrusion Prevention System (IP/IPS).
- Modules like toobusy-js can be used for monitoring the event loop.
- Monitoring modules also keep track of response time when the server gets busy or CPU consumption rises.
2. Make use of flat Promise chains to avoid layers of nesting
Asynchronous callbacks can be considered as one of the finest features of Node.js, which comes as an advantage compared to the previous basic callback functions. However, this feature can turn into one of the worst nightmares with increasing layers of nesting. For instance, if the nesting layers go beyond ten layers, it causes errors due to which results may get lost within the asynchronous callbacks.
What can be the solution to the nesting problem?
- Use flat Promise chains to avoid callback hell.
- Flat Promise chains have the potential to control programming semantics.
- It can increase the flow of code by detecting errors and exceptions.
We realized the importance of using flat promise chains while customizing matchmaking requests for pharmacy staffing solution apps called Fill-in-Rx. Since Node.js is used to retrieve callbacks on a single thread, it helped our team easily handle concurrent user requests, even at scale.
3. Avoid blocking the event loop for neat performance
While there seems to be no inherent danger to Node.js’s single-thread event-driven architecture, things get a little tricky when CPU-intensive JS operations are executed.
When a new client connection is established with the application, the EventLoop sends a response to the client. This is not just for new connections, but every incoming and outgoing request passes through the Event Loop. In any situation when the Event Loop gets blocks, new and current clients will not get a chance to establish a connection with the application.
This phenomenon is known as “blocking” that causes a threat even when a regular expression is being resolved. A block in the event loop means greater chances of security vulnerabilities.
What can be done to avoid blocking the event loop?
- Node.js gives developers the option to assign callbacks to IO-blocked events, allowing the callback to run asynchronously.
- Operations relying on one another can be written in a single non-blocking function.
- Employing the below-mentioned command function that doesn’t block the event loop: (Source: OWASP)
4. Managing uncaught exception to avoid security loopholes
For an uncaught exception, Node.js usually prints the current stack trace and terminates the entire thread. Given that all uncaught exceptions are not necessarily security vulnerabilities, Node.js allows customization of the behavior through an EventEmitter object.
Unhandled code rejections usually lead to security loopholes, and IMB realized the problem while building their applications using Node.js. Improper handlers and resource allocation over unneeded features were the reasons that caused uncaught exceptions, directly making the application vulnerable to threats. In an effort to deal with all the uncaught exceptions, they used “triggerUncaughtException” for alerting the developers and determining errors within their programming syntax in real-time.
How to leverage EventEmitter for managing uncaught exceptions?
- The EventEmitter object emits the uncaughtException to the main event loop. It is advisable to bind up the uncaughtException to the event and clear unallocated resources like handlers and file descriptors.
- Even the use of EventEmitter leaves a possibility of errors being left within the event chain, causing the application to crash unexpectedly. To avoid this, show a customized error message on display for the users instead of giving away the exact error.
5. Handle errors to prevent unauthorized attacks
Handling errors are crucial for the steady functioning of an application and preventing unauthorized attacks. You must keep in mind that the application might display or leak sensitive information like stack traces to the client during an error. The moment attackers know the application’s vulnerability, they might send repeated requests causing the application to crash or a denial of service.
To avoid this, the real-time geospatial application like Uber sends constant notifications to both drivers and users for easy connection through matchmaking and road mapping. Uber realized that error handling in Node.js is as flawless as it could get with the aid of internal components and external tools. Apart from appropriate error analysis, the framework allowed fast code deployment, which secured the application from constant false requests.
What can be done to handle errors efficiently?
- While express routes are known for handling errors, it can even crash the app if an error is unhandled within an asynchronous callback.
- An Error object can be added in the first argument to an asynchronous call to ensure that errors are handled within the callback.
- Wrap express routes with a catch clause to avoid displaying or leaking sensitive information.
- Components like a load balancer, cloud firewall, or Ngnix can be used to limit DOS attacks.
6. Limit request size to avoid DOS attacks
A necessary security concern within Node.js is to ensure that the request size is limited to avoid large request bodies sent by attackers. It is to be remembered that the bigger the payload, the difficult it is for the single thread to process the request.
This is why attackers send large amounts of requests that can drain the server memory, crash the application, or even fill up the disk space, resulting in a Denial of Service.
Anticipating a sudden rise in the user base on the ONA Dating application, we utilized Node.js for improving scalability and performance. We knew that denial of service would be a massive disappointment for its users, and crucially, a security threat.
We took this seriously, and this is how we avoided DOS attacks:
- Limiting the request size by utilizing raw-body, external tools like firewall and ELB.
- Configuring express body-parser to accept small-size payloads.
- Requesting size limits for specific content types using express middleware and validate the data against the content type stated in the request headers.
7. Set up Cookie flags for session management
Session management is a significant aspect of web applications that helps maintain security and process multiple requests over the site. Any information related to session management over a web application is sent through cookies, and inappropriate use of HTTP cookies is definitely a recipe for vulnerabilities.
How do we avoid session vulnerabilities and security threats?
- Set up cookie flags like httpOnly, Secure, or SameSite.
- httpOnly flag can be used as a measure against XSS attacks.
- The ‘Secure’ flag ensures that cookies are sent only if the communication is over HTTP.
- SameSite flag secures the session from CSRF attacks.
8. Make sure that packages are up to date
To ensure the latest security updates, I would highly recommend that all third-party packages are kept up to date, irrespective of the framework that is being used. We might be swayed away by how efficient a third-party open-source package helps in the development process, but it must be kept in mind that these same packages are among the top OWASP vulnerabilities.
What practices should be mandated to ensure that packages are up to date?
- Be aware of the third-party applications that are being used in the Node.js framework.
- Keep a tab on the security issues and developments from known bulletins.
The list of applications that we have developed using Node.js, including the ONA Dating App, Fill-in-Rx, SenTmap, are all dependent on third-party packages and components. We, at Simform, understand the security risks that come along with these packages, and therefore, have strongly enforced a monitoring system to keep an eye on the latest security updates. Through a centralized monitoring system administered by the QA team, development teams follow the updated security checklist at each and every moment of the development process.
9. Avoid using dangerous functions to maintain application stability
It is noteworthy that specific functions and modules within Node.js are considered as ‘dangerous’ functions. These can seriously comprise the platform security of the application. The two typical dangerous functions that I and all experts would warn against are the Eval function and the Chid Process function.
Apart from functions, modules may also pose a security threat. Some of these modules and functions include vm module, setTimeout function, execScript, setInterval function, and setImmediate function.
10. Use security linters and SAST for methodological testing
It is not possible to keep track of each and every security vulnerability. Hence, it is natural to overlook some that might arise out of a faulty code, component, or function. This is the very reason analytical testing methodologies are employed to scan and identify programming errors that might lead to a security breach.
How do we keep track and have a keen eye on vulnerabilities?
- The Static Analysis Security Testing can be utilized for the development and component testing process.
- SAST accelerates the development process and reduces the costs of modifying or updating applications in case of any issues in the future.
- Lint tools can automatically test code source code, identify faults, and alert the developer of the potential vulnerability within the app.
- Examples of JS linting tools used within Node.js include ESLint, JSHint, and TSLint.
It is quite common for companies to implement a centralized security testing and scanning facility for applications. Unfortunately, such centralized testing facilities do not provide much aid to the developers in identifying a threat in real-time while programming. To overcome this issue, microblogging sites like Twitter have adapted a self-service scanning by utilizing the SAST method and allowing developers to have complete control over their programming syntax.
Know about the best Node.js frameworks for web applications
Top Tools to Utilize for Enhanced Node.js Security
Security threats and vulnerabilities are part and parcel of the frontend framework. Along with the above best practices for ensuring security with Node.js, it is wise to use tools to maintain extra security from attackers and detect existing vulnerabilities.
Here is a list of some of the tools that can be used to maintain Node.js application security –
Snyk is a developer-first cloud-native application security tool that has created a reputation for scanning and fixing bugs within any container, open-source libraries, or code. The best feature of this application is its real-time monitoring facility which alerts the developers to fix particular vulnerabilities. Along with its integration ability into GitHub, Circle CI, Code Ship, Jenkins, Tarvis, and Bamboo, it has its internal repository of updated vulnerability databases for quick detection of risks and threats.
Security of HTTP headers is often ignored by developers, and it has the potential to leak sensitive information to attackers. As a middleware, Helmet is a collection of 12 Node modules and, it follows the best OWASP practices for securing headers for increasing security in Node.js. The middleware can be used in Express and Koa, allowing you to implement HTTP response headers.
3. Source Clear
Keeping track of third-party packages, components, and modules can become a time-consuming activity, even though you are supposed to keep an updated record of it. Source Clear tools make the job easy by utilizing a “vulnerable methods identification” to identify if the vulnerable dependency is used within the application. With an extensive database of its own and constant feeding updates from recent bulletins, the tool can reduce false positives and provide detailed reports of the threats within the program.
Application security and testing are principal activities required to ensure that the application is rolling out entirely safe for its clients and users. Be it websites, web applications, or APIs, Acunetix scans the entire server-side of the application and gives actionable results. The tool is capable of scanning over 7000 vulnerabilities along with scanning multi-level forms and password-protected areas of the site.
If you are looking for open-source Node.js security testing tools, Retire.js is the perfect option as a command-line scanner. While third-party packages and components would inevitably be used, Retire.js scans known vulnerabilities within the codes and triggers a warning for the developer about its use. The tool also has plugin components and browser extensions that are regularly updated from various sources for providing precise security alerts.
In my experience, security vulnerabilities and threats have cost companies thousands of dollars. While breaches can make a big hole in the pocket, sensitive data leaks and compromised information cannot be priced in simple amounts. We might not be able to stop every attack that attackers might initiate to harm our apps, but we can ensure that our carelessness does not cause extensive damage.
With this article, the intention is not just to explain the best practices one should follow while developing an application but also to take the necessary steps at the early stages of programming. This article is part of the checklist that our expert team of Nodejs developers follows at Simform.
Subscribe to our blog to keep receiving updates on Node.js security or get in touch with our developing experts to expand your team and create a secure Node.js application.