npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

node-module-confinement

v2.1.0

Published

Confining require and node_modules for the extra bit of security

Downloads

15

Readme

node-module-confinement

This package provides a simple way for confining modules and preventing them to load unwanted other modules, or redirect modules to load to different ones.

You can set up a blacklist and a whitelist for this confinement. You can tell the confinement to prevent node internal modules completely, only allowing a few ones by whitelist. Additionally, you can redirect require calls to your own modules, like redirecting fs to safe-fs, which provides a trusted subset of fs-access.

If anything defies the confinement, an error is thrown to prevent anything bad from happening. The error tells you which confinement was defied and which module did this.

On top of all that, this module comes with some "addons", which allow you to setup some traps to prevent unwanted code execution methods.

Why

Using dependencies is dangerous. Installing simple npm-packages might download tons of other unknown dependencies, which you can't review all. If only one of such dependencies contain malicious code, you're screwed.

This module helps to reduce the attack-surface, as you can limit access to node internal modules or critical configuration files. That way bad code leads to an error, preventing bad stuff from happening.

So this module helps with preventing supply-chain-attacks or dependency-attacks, and helps to secure your application by simply enforcing policies.

API

The main API exists of only a single function: setup. The signature is: setup(moduleContext, confinementConfiguration)

The moduleContext is the current module value. This is used to resolve all modules referenced by the confinementConfiguration.

The confinementConfiguration is the most important part, and looks something like this:

const confinementConfiguration = {
    defaultConfinement: {
        allowBuiltIns: true
        blackList: [],
        whiteList: [],
        redirect: {}
    },
    confinements: {
        './some/module.js': {
            applyToChildren: false,
            allowBuiltIns: false
            blackList: [],
            whiteList: ['fs', 'path'],
            redirect: {}
        },
        './some/other/module.js': {
            applyToChildren: false,
            allowBuiltIns: false
            blackList: [],
            whiteList: [],
            redirect: {}
        }
    },
    addons: {
        trapEval: false,
        trapFunction: false
    }
};

Confinements

So, bascially a confinement consists of 4 main rules: allowBuiltIns, whiteList, blackList and redirect. The rules are applied as follows:

First of let's look at whiteList and blackList, as they are the most straight forward. whiteListed modules are allowed by default, blackListed modules are denied by default. All modules passed in this arrays get resolved to their actual module-filename. To do so all modules get resolved relative to given moduleContext (which was passed as first parameter to setup-function).

The second part to consider is the allowBuiltIns flag, which tells to allow or deny all builtin nodejs modules. So if you put allowBuiltIns=false calling stuff like require('fs') will throw an error. This is basically a shortcut to add all node builtin modules to the blackList. To allow loading at least some builtin modules, you can pass them to the whiteList, so you can write a confinement like {allowBuiltIns: false, whiteList: ['fs']} to allow require('fs'), but deny all other builtins like require('path').

After applying all the rules above, we know whether the current require call is allowed or forbidden. If it's allowed, the last property comes into play: redirect. This contains a map of module -> module contents. So if the module wants to load a certain other module, you can redirect the call to load yet another module. This is useful for example when redirecting builtins to mocks or limited variants, something like:

const confinementConfiguration {
    defaultConfinement: {
        allowBuiltIns: false,
        whiteList: ['fs'],
        redirect: {
            'fs': './myfsmock.js'
        }
    }
}

That way we can redirect require calls to different modules for security purpose (or whatever).

The last option of a confinement is applyToChildren, which is a flag only applied during confinement evaluation. To know understand this flag, you have to understand how confinements get resolved:

  • First the module, that should be loaded, gets resolved to its module-filename
  • Then we check, whether the resolved module-filename matches with any confinements-entry. If so, we use this confinement
  • Because we have no specific confinement, we traverse the module-tree upwards, to check whether any parent has a confinement. If any parent has a confinement, we check the applyToChildren flag. If it's true, we use this confinement. If it's false, we traverse the tree upwards up to the root module (provided moduleContext)
  • If we couldn't find any confinement up till now, we use the defaultConfinement
  • If the defaultConfinement was empty, no confinement is applied

This should make it obvious, what applyToChildren does.

This should explain everything about confinements, that you need to know.

Addons

Another special topic is "addons". This module contains some addons, that help with securing your application. You can apply the following addons by simply setting them to true in the configuration.

trapEval

This is pretty obvious: Proxy the global eval function and throw an error each time it gets called. This way eval doesn't work anymore, and malicious eval-calls are prevented.

trapFunction

This is basically as obvious as the previous one, but most people don't know about this one: The function constructor is basically a hidden eval function. Stuff like Function('console.log(process.pid);')() might not do bad, but does the same as eval('console.log(process.pid);'), so its basically eval in disguise.

To prevent usage of this hidden eval-like stuff, you can apply a trap by enabling this option. This trap will prevent all Function(...) and new Function(...) calls and throw an error each time it happens.