NPM Dependency Security Best Practices ๐Ÿ“ฆ

The Definitive Guide to NPM Package Security

Check for Outdated Dependencies

It's important to keep dependencies up-to-datem since these packages usually undergo continuous improvements, bugs fixes and security patches.

Yarn has this functionality built in, with yarn outdated, which shows version info for all a projects installed dependencies.

A better tool for the job is npm-check (MIT) by @dylang, which will check for outdated, incorrect, and unused dependencies. This is used by just running npx npm-check from your projects root, you can also interactively update outdated packages with the --update flag, check the docs for all options.

There's also David-DM, a website that shows list of outdated dependencies for a given public git repo, with embeddable badges.


Remove Unused Packages

Large projects have a tendency to accumulate unused dependencies, causing there to be more to maintain, larger package size, and increased attack surface.

An easy way to check for unused dependencies, is with depcheck, a CLI tool for determining which of your installed packages are (likely) not being used anywhere in your code base. To use, just run npx depcheck. This tool is included within the npm-check mentioned above. It's worth noting, that this is not 100% accurate, so be careful when removing dependencies.
You can also remove any dependencies that have an overlapping range, with yarn dedupe


Audit Package Security

If a project has a large number of dependencies, then it will be more exposed to security risks. Many packages open new ports, thus increasing the attack surface, and some include vulnerabilities, a few of which are extremely severe; and open source projects regularly grow stale, neglecting to fix security flaws.

yarn audit or Snyk Test can be used to find, fix and monitor known vulnerabilities in open-source dependencies.

Sometimes these can be fixed automatically, with yarn audit fix, which will update certain semver-compatible packages to the patched version, if available.

Another option is to use a bot to scan for vulnerable packages, and submit a PR when a fix becomes available. The branch can then be pulled, tested and either merged or rejected. A popular choice for this is Dependabot. This functionality, plus more is also included with Snyk, a hosted solution for E2E application security monitoring.

If your project contains a dependency that has been identified as vulnerable, it is important to address it as quickly as possible. Either:

  • Update the package to a patched version, if available
  • Remove or replace the vulnerable package
  • Mark the issue as low risk, if you've looked into it and identified that there is no issue in your use case, such as in a dev dependency that is not used in production

It's worth noting that not all issues raised by the audit will actually have any impact on your application. Each issue still needs to be looked into to determine weather it is relevant to your use case though.


Only use Well Maintained Packages

Packages that are used by tens of thousands of projects are usually more closely monitored by other developers and security researchers, as opposed to smaller projects with very few regular users. This is important, as security (and other issues) will likely be spotted sooner, and addressed quicker in better maintained projects.

One way to assess package usage is its download rate, indicated in "Stats" section of the npmโ€™s package page, or using npm-stat.com for historic download data. You should also check the projects GitHub page, see what to look for below.


Analysing Package Sizes

Bundlephobia is a super useful tool for checking any packages size and bundle impact prior to installing. There's also cost-of-modules, a CLI tool for locally analysing the size of the significant packages within your project. Just run npx cost-of-modules within your projects root.


Respecting Package Licenses

Open-Source components are published either under a permissive or a copyleft license. Before adding any package, it's important to check the license. Some common open source software licenses, such as GPL will prevent you from using their packages unless you also open source your code, and many packages (such as MIT) require you to correctly give attribution to the original author. To quickly understand what any given license allows you to do, TDLR; Legal is a useful resource.

There are also tools to help you automate the process of license checking within your project. The most notable hosted solution is FOSSA, which will automatically scan your project for dependency licenses and notify you of any issues and breach of licenses. There's also license-checker, a CLI tool which runs locally, and will output a list of licenses used by dependencies. This can be used by running npx license-checker from the root of your project


Ignore Run Scripts

NPM script hooks allow code to run at various points of the package life cycle. This is often used for general housekeeping tasks, but it can also be abused by bad actors to execute malicious code (such as the eslint-scope incident and the crossenv incident). Such attacks can usually be avoided by properly vetting new packages as well as upgrades. Adding the ignore-scripts option to your .npmrc file will prevent packages from running arbitrary commands.


Package Health Checks

The npm doctor command is useful for ensuring that everything is in order, and should be integrated into your automated checks. It will do the following:

  • Check the official npm registry is reachable, and display the currently configured registry.
  • Check that Git is available.
  • Review installed npm and Node.js versions.
  • Run permission checks on the various folders such as the local and global node_modules, and on the folder used for package cache.
  • Check the local npm module cache for checksum correctness.

Considerations when adding new Packages

A lot of the above issues can be mitigated, just by being thoughtful about what your installing into your project. Think about weather you really need another new dependency, ensure there's nothing similar already included in your app, and then thouroughly check the project out, being sure to validate the following criteria:

  • When was it last updated?
  • How often are releases published?
  • How many developers have contributed to it? (More is better, as there is a lower risk of the project being abandoned, and a higher chance that issues are spotted).
  • How many open issues are there?
  • Does it have a security policy?
  • Any open or recent security advisories?
  • Is it correctly using semantic versioning?
  • Is it thoroughly tested?
  • Does it use many additional dependencies? If so, are they to be deemed trustworthy?
  • How many other projects rely on it?
  • Is it frequently downloaded? (Check the "Stats" section of the NPM page, or use npm-stat.com)
  • Is the code clear and readable? If it is, then issues will be much easier to spot
  • Is the license compatible with your project, and have you acknowledged the terms (e.g. attribution)
  • How large is the package? And how will this affect your project's bundle size

For quick info about any package, you can also run npm view [package-name]


Strict Installs

When setting up your project in the CI environment, it is reccomended to use the CI install command. This is more strict than a regular install, which can help catch errors or inconsistencies caused by the incrementally-installed local environments of most npm users. It's also significantly faster, since it skips certain user-oriented features. Just replace npm i, with npm ci, or for yarn use: yarn install --frozen-lockfile.


Lockfiles

In order to ensure reproducible builds, yarn needs to know exactley which version of each package should be installed. This is specified in the yarn.lock file (or npm-lock.json for NPM), and is auto-generated on install. This file should be commited to git, as it will prevent updates in a packages minor version causing any unexpected errors in on other machines.


Registry

Using your own registry, such as Verdaccio which can be privatley self-hosted, gives you far greater control over your NPM downloads. It allows for easy usage of private packages, and also gives you the ability to proxy other registries, and cache the downloaded of modules along the way, for improved speed and control, you can link multiple registries and override public packages. Alternative options include using NPM's enterprise offering, or installing dependencies directly from private git repos.


Dependency Inversion Principle

When writing your own modules, follow the SOLID principles, specifically in terms of the coupling between the different classes or modules. Writing small, maintainable, tested, single-purpose packages, which will then be easier to manage in terms of security and quality.


Secure your NPM Account

If you're publishing any of your modules to the NPM registry, then secure access is very important. Ensure that you've enabled 2FA, and keep your account credentials and tokens safe. If your not making any updates, then login using read-only mode. Make use of NPM author tokens for publishing and updating packages.


Watch out for Typosquatting Attacks

Typosquatting is an attack that involves packages with slight naming changes, that are often installed by mistake due to a typo. Since packages can have access to environmental variables, which often include credentials, tokens and API keys, a malicious actor can harvest this sensitive info.

To protect yourself:

  • Take care when copying and pasting commands into the terminal and always verify the source of the repo that you are installing via npm info.
  • Default to having an npm logged-out user in your daily work routines so your credentials wonโ€™t be the weak spot that would lead to easily compromising your account.
  • Consider appending the --ignore-script flag when installing packages, to prevent them from executing arbitrary code.

Tips and Tricks

  • Use any NPM CLI package, without having to first install it, using npx
  • While developing multiple packages simultaneously, yarn link is useful for creating symbolic links between them
  • Due to how NPM dependencies are structured, if you've been working on a lot of projects, you can find your disk space frequently running low. NPKill is a handy CLI util, to find all node_modules directories for easy deletion of the ones you no longer need. Just run npx npkill to get started. You can also do this nativley
  • To open the GitHub page for any package, just run npm docs [package-name]
  • If your facing any issues with NPM, then running npm doctor with troubleshoot common issues, and output the likely fix
  • List all installed packages in a tree structure with npm ls
  • Many of the NPM short commands can be grouped together, for example npm cit is the same as fresh install and run tests
  • When working with mono-repos or multiple packages, you can use the --prefix [./path/to/dir] flag to run NPM commands in other directories, without having to cd
  • To view all valid release versions of a given package, run npm view [package-name] versions, or yarn info [package-name] versions
  • View a tree of dependencies for any package, with npmgraph.js.org
  • Use different NPM versions for different projects easily with Volta
  • More info can be found for any CVE via cvedetails.com or using GitHub Advisories.

You'll only receive email when they publish something new.

More from Alicia's Notes ๐Ÿš€
All posts