37 Package Management and Distribution Through Npm for Package Management and Sharing

37 Package Management and Distribution Through NPM for Package Management and Sharing #

Hello, I am Ishikawa.

In the previous lessons, we have seen that whether it is the reactive programming framework React, the testing tools Jest and Puppeteer, or the code linting and styling optimization tools ESLint and Prettier, they all rely on third-party libraries. In our previous examples, we downloaded and installed these tools using NPM.

Therefore, today we will take an in-depth look at NPM and package management and publishing.

Package Publishing #

Although NPM (Node Package Manager) is called Node package management, it can actually be used to manage or publish packages written in languages other than JavaScript. However, the main audience of NPM is still JavaScript developers in web and server-side development. In NPM, there are two core concepts: packages and modules.

A package is a folder that contains a package.json file. The package.json file describes the files or directories in the package. A package must have a package.json file in order to be published to the NPM registry.

A module is any file or directory that can be loaded by Node.js using the require() function. A module that can be successfully loaded must be a directory with a package.json file containing a main field, or a JavaScript file.

Publishing a package is very simple. First, we can create a package folder and navigate to the root directory of the package using the command line by using the mkdir and cd commands. In the directory, we can create a package.json file. There are two ways to create a package.json file: directly creating it or executing the npm init command on the command line and generating the package.json file by following the prompts.

In a package.json file, the required fields are the name and version. Sometimes, a package has dependencies on other packages. In this case, the relevant dependencies can also be set in the package.json file.

If the module is managed on Git, it can be added to the package in the root directory of the package. Alternatively, we can also choose to write a module directly in the directory. Taking the printMsg module as an example, the module exported through export can be imported and used in other programs through require().

After creating a package, it’s best to test it locally using npm install before publishing it. Once everything is confirmed to be correct, the package can be published using npm publish. Whether it’s publishing a public or private package, a user must be created on the NPM registration page before publishing. For security reasons, it’s recommended to enable two-factor authentication (2FA) before publishing a package.

The above process is for publishing public packages. Apart from public packages, we can also publish private packages. So what’s the difference between public and private packages? For public packages, everyone has read and download permissions. Write and publish permissions can be set for specified users or organizations. For private packages, only members from specified individuals or organizations can have read, download, write, or publish access to the relevant packages.

Continuous Integration and Deployment with NPM #

In DevOps, we often emphasize the continuous integration and deployment of CI/CD. When using NPM, we can also replace the authentication method of username and password with an access token. The access token is a hexadecimal string that we can use for authentication, module installation, or publication. This way, we can allow other tools, such as the CI/CD testing environment, to access NPM packages. When our workflow is running, it can complete tasks including installing private packages.

There are two types of tokens: traditional tokens and granular tokens. Traditional tokens are mainly divided into three categories. One is read-only, which only allows package downloads; the second is read and install, which allows installations in addition to downloads; the third is read, install, and publish, which allows package publication on top of the first two. However, from a security perspective, granular tokens, as the name suggests, have finer-grained permission management. Granular tokens can better differentiate the packages and scopes that can be accessed, authorize specific organizations, set expiration times, control authorization IP ranges based on CIDR, and provide options for read-only and read-write permissions.

For example, we can create an access token based on an IP range using the following command:

npm token create --cidr=192.0.2.0/24

Afterwards, we can set the token as an environment variable or key in the CI/CD server. For example, in GitHub Actions, we can add the token as a secret key. Then, using this secret key, we create an environment variable named NPM_TOKEN and provide the key to the workflow.

steps:
  - run: |
      npm install
  - env:
      NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

It is worth emphasizing that this token has permission to read private packages, which means it can publish new packages, modify user or package settings. Therefore, from a security perspective, we must protect the token. Under no circumstances should the token be added to version control or stored in an insecure location. Ideally, the token should be stored in a password manager, a secure storage provided by a cloud provider, or a secure storage provided by the CI/CD tool. If possible, we should use the granular access token with the lowest permissions mentioned earlier and set a shorter expiration time for the token.

Package Management #

Previously, we discussed how we can choose between making a package public or private depending on the permissions. Regardless of the mode, we can manage users and organizations. The only difference is that public packages can be read and downloaded by anyone, while private packages require specific users or organizations for permission to read, download, or write and publish. Now, let’s take a look at how to manage organizations in NPM.

First, after logging in, click on the avatar and select “Create an Organization”. Then give the organization a name. At this point, you can choose between free and paid options. The difference between the two is that the free version allows you to create public packages, while private packages require payment. During this process, you can also choose to convert an individual account into an organization account.

Next, you can invite users to join the organization by their name and email. You can choose the role of the user and the team they will be added to. For example, the roles can be “Member” or “Admin”. Only the owner has the right to add or remove members from the organization, change their roles, or modify/delete the organization’s name. “Admins” can manage teams, which includes creating and deleting teams, as well as managing the addition and removal of team members and package access permissions. “Members” have the permission to create and publish packages within the organization.

During the organization creation process, a “Developers” team is automatically generated. In addition to this, the organization owner and admins can create more teams. For example, for certain users that you do not want to grant development permissions, you can move them from the “Developers” team to a project management team.

Security Considerations #

Previously, we learned about the publishing, CI/CD, and management processes of NPM. Now, let’s take a look at the security considerations of NPM. When using NPM, it is important to consider security from both the perspective of the developer and the user.

First, let’s focus on the perspective of developers. The most critical aspect to be cautious about is the security of our accounts.

Subaccount security is particularly vulnerable to password attacks. Password attacks are common types of cyber attacks and can occur on any web service, not just NPM. The best way to protect account security is to enable two-factor authentication (2FA), as we mentioned before.

On top of that, the strongest security option is to use security keys, whether they are built-in to the device or external hardware keys. Security keys bind authentication to the site being accessed, significantly reducing the risk of phishing. However, since not everyone has access to security keys, NPM also supports authentication applications that generate one-time passwords for 2FA.

Due to the frequency of such attacks, the popularity of NPM packages, and their impact on the open-source ecosystem, NPM has adopted a phased approach to enforce 2FA authentication for the top 100 package maintainers and top 500 package contributors. Of course, this measure is still far from sufficient. In the near future, all high-impact package maintainers (packages downloaded over 1 million times per week or packages with over 500 dependencies) will be required to undergo 2FA authentication. If you, as a package publisher, do not choose 2FA verification, NPM will strengthen login authentication by sending you a one-time password via email to prevent account theft.

Another method of hijacking accounts is using expired domains as email addresses to identify accounts. Attackers can register expired domains and re-create email addresses used to register accounts. By accessing the registered email address associated with the account, attackers can steal accounts that are not protected by 2FA by resetting the password.

When a package is published, the email address associated with the account is included in the public metadata. Attackers can exploit this public data to identify accounts that are susceptible to theft. NPM also regularly checks if account email addresses have expired domains or invalid MX records. After a domain expires, NPM disables account password resets and requires users to go through an account recovery or successful authentication process before resetting the password. It is important to note that as a package maintainer, when you update your email address, the email address stored in the public metadata of the package will not be updated. Since scraping public metadata to identify accounts affected by expired domains can lead to false positives, these accounts may appear vulnerable to attacks but they are actually not.

Now, let’s look at it from the perspective of users. Attackers may try to deceive others into installing malicious packages by registering packages with names similar to popular packages. They hope that people will make typos in the package names or confuse the two packages in some other way. NPM can detect typo squatting attacks and prevent the publication of such packages. A derivative attack of this nature occurs when a public package takes the place of a private package used by an organization due to having the same name. Therefore, a suggestion here is to use scoped packages to ensure that private packages are not replaced by packages from the public registry.

Another issue is malicious changes to existing packages. In this case, attackers may not try to deceive users with similarly named packages but attempt to add malicious behavior to popular existing packages. To address this issue, NPM collaborates with Microsoft to scan known malicious content in packages and run packages to identify potential new patterns of malicious behavior. This greatly reduces the proportion of NPM packages that contain malicious content. Additionally, NPM’s Trust and Security team checks and removes any reported malicious behavior and content.

Summary #

From this lecture, we have seen that in the increasingly modularized world of JavaScript, NPM makes it easier for us to share and use tools provided by other developers. Additionally, besides the functionality of the modules themselves, NPM also serves as a good version and dependency management tool, and its use can also facilitate team collaboration. However, at the same time, we have also seen many security risks associated with it. Therefore, caution should be exercised when using it. As developers, we do not want the tools we kindly provide to become a means for hackers to launch attacks. Similarly, as users, we should be more mindful of the risks associated with managing third-party code downloaded from NPM.

Reflection question #

Previously, we talked about the importance of considering security when using NPM. In addition to the methods mentioned earlier, we also mentioned vulnerability scanning as a way to reduce risks during the security lecture. In actual development, would you use NPM? Apart from the methods we discussed, can you think of other security measures?

Feel free to share your experiences, exchange learning insights, or ask questions in the comments section. If you found today’s content helpful, please share it with more friends. See you in the next class!