NPM 安全最佳实践¶
在以下 npm 速查表中,我们将重点介绍 10 项 npm 安全最佳实践和对 JavaScript 和 Node.js 开发者有用的生产力技巧。
1) 避免将敏感信息发布到 npm registry¶
无论您使用的是 API 密钥、密码还是其他敏感信息,它们都很容易泄露到源代码管理中,甚至是公共 npm registry 上的已发布包中。您在工作目录中可能将敏感信息存储在指定文件(例如 .env
)中,这些文件应该添加到 .gitignore
中以避免提交到 SCM,但当您从项目目录发布 npm 包时会发生什么?
npm CLI 将项目打包成 tar 归档文件(tarball),以便将其推送到 registry。以下标准决定哪些文件和目录会添加到 tarball 中
- 如果存在
.gitignore
或.npmignore
文件,则在准备发布包时,这些文件的内容将用作忽略模式。 - 如果两个忽略文件都存在,则不在
.npmignore
中的所有内容都将发布到 registry。这种情况是常见的混淆来源,可能导致敏感信息泄露。
开发者可能会更新 .gitignore
文件,但忘记同时更新 .npmignore
,这可能导致一个潜在的敏感文件未被推送到源代码管理,但仍然包含在 npm 包中。
另一个值得采用的最佳实践是使用 package.json 中的 files
属性,它充当白名单,指定要包含在创建和安装的包中的文件数组(而忽略文件则充当黑名单)。files
属性和忽略文件可以一起使用,以确定哪些文件应该明确地包含在包中以及从包中排除。当两者都使用时,package.json 中的 files
属性优先于忽略文件。
当一个包发布时,npm CLI 会详细显示正在创建的归档文件。为了格外小心,请在发布命令中添加 --dry-run
命令行参数,以便在实际发布到 registry 之前先查看 tarball 是如何创建的。
2019 年 1 月,npm 在其博客上分享说,如果他们检测到某个令牌已随包发布,他们就增加了一个自动撤销令牌的机制。
2) 强制使用 lockfile¶
我们张开双臂迎接了包 lockfile 的诞生,它带来了:跨不同环境的确定性安装,以及在团队协作中强制执行依赖项期望。生活真美好!我本以为如此……如果我偷偷地修改了项目的 package.json
文件,却忘记同时提交 lockfile,那会发生什么呢?
Yarn 和 npm 在依赖项安装过程中行为相同。当它们检测到项目 package.json
和 lockfile 之间存在不一致时,它们会根据 package.json
清单来弥补这种变化,安装与 lockfile 中记录的版本不同的版本。
这种情况对构建和生产环境可能很危险,因为它们可能会引入意外的包版本,并使 lockfile 的所有好处都化为乌有。
幸运的是,有一种方法可以告诉 Yarn 和 npm 遵守从 lockfile 中引用的指定依赖项及其版本集。任何不一致都将中止安装。命令行应如下所示
- 如果您使用 Yarn,请运行
yarn install --frozen-lockfile
。 - 如果您使用 npm,请运行
npm ci
。
3) 通过忽略运行脚本来最小化攻击面¶
npm CLI 可与包运行脚本配合使用。如果您曾运行 npm start
或 npm test
,那么您也曾使用过包运行脚本。npm CLI 基于包可以声明的脚本构建,并允许包定义在项目安装过程中在特定入口点运行的脚本。例如,其中一些脚本钩子条目可能是 postinstall
脚本,安装中的包将执行这些脚本以执行清理工作。
凭借此功能,恶意攻击者可能会创建或修改包,通过在包安装时运行任意命令来执行恶意行为。我们已经看到一些此类事件发生,例如流行的eslint-scope 事件,它窃取了 npm 令牌,以及crossenv 事件,以及其他 36 个利用 npm registry 上的拼写劫持攻击的包。
应用这些 npm 安全最佳实践,以最小化恶意模块攻击面
- 始终审查并对您安装的第三方模块进行尽职调查,以确认其健康状况和可信度。
- 不要立即升级到新版本;允许新包版本流通一段时间后再尝试使用。
- 升级前,请务必查看升级版本的变更日志和发布说明。
- 安装包时,请务必添加
--ignore-scripts
后缀,以禁用第三方包执行任何脚本。 - 考虑将
ignore-scripts
添加到您的.npmrc
项目文件,或添加到您的全局 npm 配置中。
4) 评估 npm 项目健康状况¶
npm outdated 命令¶
如果在不审查发布说明、代码更改和未全面测试新升级的情况下,急于不断将依赖项升级到最新版本,这不一定是个好习惯。尽管如此,长期不更新或根本不更新也会带来麻烦。
npm CLI 可以提供您所用依赖项的新旧程度信息,以及它们与语义版本控制偏差的关系。通过运行 npm outdated
,您可以看到哪些包已过时。黄色的依赖项对应于 package.json 清单中指定的语义版本,而红色的依赖项表示有可用更新。此外,输出还会显示每个依赖项的最新版本。
npm doctor 命令¶
在各种 Node.js 包管理器以及您路径中可能安装的不同版本的 Node.js 之间,您如何验证健康的 npm 安装和工作环境?无论您是在开发环境还是 CI 中使用 npm CLI,评估一切是否按预期工作都很重要。
呼叫医生!npm CLI 包含一个健康评估工具,用于诊断您的环境以确保 npm 交互正常工作。运行 npm doctor
来检查您的 npm 设置
- 检查官方 npm registry 是否可达,并显示当前配置的 registry。
- 检查 Git 是否可用。
- 检查已安装的 npm 和 Node.js 版本。
- 对各种文件夹运行权限检查,例如本地和全局的
node_modules
,以及用于包缓存的文件夹。 - 检查本地 npm 模块缓存的校验和正确性。
5) 审计开源依赖项中的漏洞¶
npm 生态系统是所有其他语言生态系统中最大的应用程序库存储库。registry 及其中的库是 JavaScript 开发者的核心,因为他们能够利用他人已构建的工作并将其整合到自己的代码库中。尽管如此,应用程序中开源库的日益普及也带来了引入安全漏洞的风险增加。
许多流行的 npm 包已被发现存在漏洞,如果没有对项目依赖项进行适当的安全审计,可能会带来重大风险。一些例子包括 npm 的 request、superagent、mongoose,甚至还有与安全相关的包,如 jsonwebtoken 和 validator。
安全性不仅仅是在安装包时扫描安全漏洞,还应与开发人员工作流程相结合,在整个软件开发生命周期中有效实施,并在代码部署后持续监控
- 扫描第三方开源项目中的安全漏洞
- 监控项目清单的快照,以便在新的 CVE 影响它们时接收警报
6) 使用本地 npm 代理¶
npm registry 是所有 JavaScript 开发者可用的最大包集合,也是大多数 Web 开发者开源项目的所在地。但有时您可能在安全性、部署或性能方面有不同的需求。在这种情况下,npm 允许您切换到不同的 registry
当您运行 npm install
时,它会自动与主 registry 建立通信以解析所有依赖项;如果您希望使用不同的 registry,那也相当简单
- 设置
npm set registry
以配置默认 registry。 - 对单个 registry 使用参数
--registry
。
Verdaccio 是一个简单轻量级、无需配置的私有 registry,安装它就像这样简单:$ npm install --global verdaccio
。
托管自己的 registry 从未如此简单!让我们看看这个工具最重要的功能
- 它支持 npm registry 格式,包括私有包功能、作用域支持、包访问控制以及 Web 界面中的认证用户。
- 它提供了连接远程 registry 的能力,以及将每个依赖项路由到不同 registry 和缓存 tarball 的功能。为了减少重复下载并节省本地开发和 CI 服务器的带宽,您应该代理所有依赖项。
- 默认情况下,它使用 htpasswd 安全作为身份验证提供程序,但也支持 Gitlab、Bitbucket、LDAP。您也可以使用自己的。
- 使用不同的存储提供商可以轻松扩展。
- 如果您的项目基于 Docker,使用官方镜像将是最佳选择。
- 它为测试环境提供了非常快的启动速度,并且方便测试大型单体仓库项目。
7) 负责任地披露安全漏洞¶
当发现安全漏洞时,如果未经事先警告或未采取适当的补救措施而向无法保护自己的用户公开,则可能构成潜在的严重威胁。
建议安全研究人员遵循负责任的披露计划,这是一套旨在将研究人员与受漏洞影响资产的供应商或维护者联系起来的流程和指南,以便传达漏洞、其影响和适用性。一旦漏洞得到正确分类,供应商和研究人员会协调漏洞的修复和发布日期,以期在安全问题公开之前为受影响的用户提供升级路径或补救措施。
8) 启用双重身份验证 (2FA)¶
2017 年 10 月,npm 正式宣布支持为使用 npm registry 托管其封闭和开源包的开发者提供双重身份验证 (2FA)。
尽管 npm registry 已经支持 2FA 一段时间了,但其采用似乎进展缓慢,一个例子是 2018 年年中发生的 eslint-scope 事件,当时 ESLint 团队的一个开发者账户被盗,导致恶意攻击者发布了恶意版本的 eslint-scope。
启用 2FA 是 npm 安全最佳实践中一个简单而重要的胜利。registry 支持两种在用户帐户中启用 2FA 的模式
- 仅授权模式——当用户通过网站或 CLI 登录 npm,或执行其他操作集(例如更改个人资料信息)时。
- 授权和写入模式——个人资料和登录操作,以及写入操作(例如管理令牌和包),以及对团队和包可见性信息的次要支持。
为自己配备一个身份验证应用程序,例如 Google Authenticator,您可以将其安装在移动设备上,然后就可以开始使用了。启用帐户的 2FA 扩展保护的一种简单方法是通过 npm 的用户界面,它允许非常轻松地启用。如果您是命令行用户,在使用受支持的 npm 客户端版本(>=5.5.1)时也很容易启用 2FA。
npm profile enable-2fa auth-and-writes
按照命令行说明启用 2FA,并保存紧急身份验证代码。如果您希望仅为登录和个人资料更改启用 2FA 模式,则可以在上面代码中将 auth-and-writes
替换为 auth-only
。
9) 使用 npm 作者令牌¶
每次您使用 npm CLI 登录时,都会为您的用户生成一个令牌,并验证您到 npm registry。令牌使得在 CI 和自动化过程中执行与 npm registry 相关的操作变得容易,例如访问 registry 上的私有模块或从构建步骤发布新版本。
令牌可以通过 npm registry 网站以及 npm 命令行客户端进行管理。以下是使用 CLI 创建限制为特定 IPv4 地址范围的只读令牌的示例
npm token create --read-only --cidr=192.0.2.0/24
要验证为您的用户创建了哪些令牌,或者在紧急情况下撤销令牌,您可以使用 npm token list
或 npm token revoke
。
通过保护和最小化 npm 令牌的暴露,确保您遵循此 npm 安全最佳实践。
10) 了解模块命名约定和拼写劫持攻击¶
命名模块是创建包时您可能做的第一件事,但在确定最终名称之前,npm 定义了一些包名称必须遵循的规则
- 长度限制为 214 个字符
- 不能以点或下划线开头
- 名称中不能包含大写字母
- 不能有尾随空格
- 只能是小写字母
- 不允许使用某些特殊字符:“~\'!()*”)’
- 不能以 . 或 _ 开头
- 不能使用 node_modules 或 favicon.ico,因为它们被禁用
- 即使您遵循这些规则,请注意 npm 在发布新包时会使用垃圾邮件检测机制,该机制基于评分以及包名称是否违反服务条款。如果违反条件,registry 可能会拒绝请求。
拼写劫持是一种依赖用户错误(例如打字错误)的攻击。通过拼写劫持,恶意攻击者可以将恶意模块发布到 npm registry,其名称与现有流行模块非常相似。
我们一直在跟踪 npm 生态系统中的数十个恶意包;它们也出现在 PyPi Python registry 上。也许一些最受欢迎的事件是针对 cross-env、event-stream 和 eslint-scope。
拼写劫持攻击的主要目标之一是用户凭据,因为任何包都可以通过全局变量 process.env 访问环境变量。我们过去看到的其他例子包括 event-stream 事件,其中攻击者针对开发者,希望将恶意代码注入到应用程序的源代码中。
以下是结束我们十项 npm 安全最佳实践列表的提示,以降低此类攻击的风险
- 在将包安装说明复制粘贴到终端时要格外小心。请务必在源代码仓库和 npm registry 上验证这确实是您打算安装的包。您可以使用
npm info
验证包的元数据,以获取有关贡献者和最新版本的更多信息。 - 您的日常工作中默认使用已登出的 npm 用户,这样您的凭据就不会成为导致帐户轻易被泄露的弱点。
- 安装包时,添加
--ignore-scripts
以降低任意命令执行的风险。例如:npm install my-malicious-package --ignore-scripts