ClickCease

Npm Security Best Practices

Cover image

Using and sharing reusable build-blocks, like Lego, is the de facto norm when it comes to web development. With npm (Node Package Management), adding new open source packages to applications is simpler and more accessible than ever.

There are almost 1.5 million packages available in the public npm registry and up to 90% of the code in modern applications is open source code developed by others. With the large number of npm packages it’s inevitable to attract hackers with malicious intent, which nowadays makes dependency management more of an issue.

Are you aware of issues and in control of your dependencies?

Download our free checklist for npm security best practices (PDF)

Download and print the free PDF to double check that your organization is in control of security and licenses for your dependencies. The PDF is free to share/repost.

Free PDF with 10 npm best practices

The 10 best practices in the PDF are described in more detail below:

  1. Add an extra layer of security by using a private registry
  2. Continuously scan and monitor all packages for security issues
  3. Stay on top of your open source licenses
  4. Enable a dependency firewall to block packages at the door
  5. Shift the responsibility for dependencies from individuals to teams
  6. Don’t run scripts by default when installing packages
  7. Be aware of typosquatting risks
  8. Keep your tokens and passwords secure and secret
  9. Build applications using the exact same package versions
  10. Make sure the whole team uses the private registry

1. Add an extra layer of security by using a private registry

Using a private registry, like Bytesafe, adds a central hub for all your packages. A layer where you are in control and aware of the packages your team is using. Configure according to your corporate policies and add security policies to only allow approved packages.

Using a private registry also allows for caching of packages. Instead of depending directly on the public registry npmjs.org and opening up for security issues. Many developers do not use 2-factor authentication and accounts get hacked all the time due to insecure passwords.

Do you have strict security requirements? Not a problem, just setup a curated firewall registry where every package version has been approved by your security team. Connect team members directly or indirectly to the registry for complete control of the packages available. You might also need to adapt your workflow to balance flexibility vs security requirements.

More info about using a private registry.

2. Continuously scan and monitor all packages for security issues

The npm team has made great efforts to improve security together with the community as a whole. New malicious packages are continuously detected and added to our advisory database.

The challenge is that there often is a delay between news of new threats and until teams have become aware of the problem. The delay is even greater until the security issues have been resolved by removing, updating or patching the affected packages or versions.

That is why you should make sure that all the packages AND their dependencies are continuously scanned for security issues and enable automatic alerts of new issues. Don’t just rely on triggered scans (like npm audit) during installation.

More info on how to scan for security issues.

3. Stay on top of your open source licenses

Using a package with the wrong license could have catastrophic consequences. License information can be stored in any file of a package, not only package.json, so don’t think of licenses as an afterthought!

  • Use tools like Bytesafe to identify license information in all files
  • Packages can have multiple licenses. In theory any piece of code can have its own license
  • Unlicensed packages are also a problem. How are you to make sure that you are allowed to use the package?
  • Restrict problematic or unlicensed packages
  • Scan all package files for problematic licenses. Get notified when license issues are identified

More info on how to scan for license issues.

4. Enable a dependency firewall to block packages at the door

Being notified is very important, but most of the time it’s even better to block the bad packages at the door. Our recommendation is to set up a code supply chain that restricts packages from being added to your private registries if they have not been scanned, are insecure or contain specific restrictive licenses.

More information on how to use policies to block packages.

5. Shift the responsibility for dependencies from individuals to teams

Even when you use a private registry you should always make intentional changes when you add new packages to a project. The more packages you use, the greater the risk that one of those packages contain a security vulnerability. Keeping all those packages up to date and secure only gets worse the larger your dependency list grows.

Making sane choices on dependencies also shouldn’t be the responsibility of single individuals, instead the whole team should take responsibility and discuss the approach to use.

Tools like Bytesafe, that cache and visualize dependencies for all its users, can be used to democratize this information and make it available to all team members.

6. Don’t run scripts by default when installing packages

When installing packages there are often scripts executed as part of the installation process. The feature is convenient and useful, but executing random scripts is also a major risk. Make sure you know what is executed when installing packages. If you are in a rush and have not checked the scripts, you are much more likely to be safe installing with the --ignore-scripts attribute.

npm install PACKAGE@VERSION --ignore-scripts

7. Be aware of typosquatting risks

Having the belief that you are installing an official package will not help if you’re instead installing a malicious package.

There are numerous examples where bad actors have published packages with similar names to official packages. The intent is to piggyback on the communal trust for popular packages and to include their malicious code instead. Often the affected packages work just like the real ones, to avoid detection for longer. Example includes twilio-npm mimicking the real package name twilio.

Make sure you double check what you install. Don’t automatically accept install instructions as a trusted truth. Review before you run!

More information about typosquatting.

8. Keep your tokens and passwords secure and secret

If you are publishing packages to a public repository, it’s a good idea to centralize the token management. Store the maintainer token and publish with Bytesafe. Avoid the risk and hassle of distributing the token to all developers.

Avoid accidental exposure of sensitive credentials. Even though npm has added features to detect secrets, make it a habit to update your ignore files (e.g. .npmignore, .gitignore etc).

More information how to configure registries to publish packages to public registries.

9. Build applications using the exact same package versions

Getting consistent and deterministic results across different environments is an ongoing issue for any dev team.

Unless the correct commands are run, or the state of project files are perfectly in sync, it is very easy to get a slightly different set of package versions installed as your node_modules. Even though you were trying to replicate another person’s environment.

The issue is even more tricky if you have multiple dev teams and different environments (Dev, QA/Test, Prod) that all want to be able to replicate a specific state.

And most critical, you always want your production CI/CD pipeline to build with the exact same package versions you developed and tested with.

To succeed, your teams should be skilled in the use of lock files (package-lock, yarn-lock, shrinkwrap etc.), keep them up to date and committed to the team’s repository.

Additionally, any user that only wants to replicate a specific state should make use of the npm clean install command, npm ci. This will try to install the exact versions to replicate a specific state of node_modules. In most cases this works, but does not cover transitive dependencies (dependencies of dependencies).

Any easier solution? For teams that require fully deterministic results Bytesafe offers the Freeze policy. The freeze policy makes a whole registry read-only allowing for fully consistent results. You are able to snapshot exactly what versions were used and use that snapshot regardless of environment. As ALL dependencies are frozen, this also includes transitive dependencies that are reproduced exactly.

More information on how to freeze states for consistent test and builds.

10. Make sure whole team uses the private registry

Your team’s code supply chain is only as strong as its weakest link. Ensure all packages flow through the private registry and change the direct dependency to npmjs to an indirect one instead.

Connect additional package sources to Bytesafe (git repositories or other private/public registries) to get a true central hub for all packages.

npm config set registry 'https://example.bytesafe.dev/r/default/'

Want to know more? We can help you

Do you have questions on how to best setup your teams code supply chain or need assistance reviewing how your teams manage package dependencies? You can email me at daniel@bytesafe.dev and I’ll gladly set up consultation or answer your questions directly.