DEV Community

Cover image for Keep malicious Node modules behind bars!
Dan Dascalescu
Dan Dascalescu

Posted on

Keep malicious Node modules behind bars!

When you use a Node module, have you thought that maybe a dependency of a dependency that your script uses, could become malicious?

That dependency has the same access to your computer as your own code. It could look through your files then phone home anything it wants, as recently seen with crypto wallets in the event-stream attack, or sensitive Linux files. These types of attacks are called "open source supply chain attacks", and hundreds of malicious package attacks have been documented.

Deno forbids access to the filesystem by default, and you can whitelist specific directories. Node doesn't have this capability.

One solution is to run your scripts in a sandboxed environment such as firejail, bubblewrap, or a VM (Docker is a containerization solution, not aimed at security).

Once you've installed and configured sandboxing, it would help to check if your scripts are indeed sandboxed correctly, and abort immediately otherwise, so that no dependency has a chance to execute. To that purpose, I've published a module called "behind-bars", which does exactly that. All you have to do is add this line at the top of your script:

import 'behind-bars';
Enter fullscreen mode Exit fullscreen mode

The module will call process.exit() immediately if it can access sensitive files or directories (browser profiles, cryptocurrency wallets, ~/*_history etc). It does so before any other imported module has a chance to run and steal data, by only using synchronous calls. You can optionally configure it to also ensure there's no Internet access, and you can define your own custom paths to sensitive files.

So far it checks for common sensitive files on Linux and MacOS systems. PRs for Windows are welcome in the repo!

Implementation challenges

To provide the maximum level of security, the module code needs to execute before any other imported code. Thus the developer must add the import 'behind-bars' line first at the top of the script. At that point, the module code needs to complete two types of requests before any other code runs:

  1. Checking the filesystem for accessible paths
  2. Checking for Internet access (by fetching a specified URL)

Both these requests are normally asynchronous, which means other module code will get executed before they complete. This is unacceptable. Starting with Node v14.3.0, top-level await has been unflagged, and we could use it to wait for the requests to complete. The problem is, when transpiling to CommonJS, top-level await doesn't actually block the other imports from executing.

This leaves using synchronous requests as the only backwards-compatible option. Unfortunately this means using old libraries like glob and sync-requests, which pull a total of ~40+ dependencies. If dropping CommonJS compatibility were ok, we could use the far lighter tiny-glob, and node's native http/https module.

Top comments (2)

Collapse
 
maxarias profile image
Maximiliano Arias

Bundlers/Node should have something like this by default. Cool idea 👏

Collapse
 
dandv profile image
Dan Dascalescu