npm-security-best-practices

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

npm Security Best Practices

npm安全最佳实践

Skill by ara.so — Security Skills collection.
This skill provides expert guidance on securing npm package installations, preventing supply chain attacks, and implementing security best practices for Node.js development. Based on the comprehensive npm-security-best-practices repository by Lirantal.
ara.so提供的技能 —— 安全技能合集。
本技能提供了npm包安装安全防护、供应链攻击防范以及Node.js开发安全最佳实践的专业指导,基于Lirantal的npm-security-best-practices综合仓库内容。

Overview

概述

The npm ecosystem is a frequent target for supply chain attacks including:
  • Shai-Hulud attacks - Worm-like propagation through compromised packages
  • Nx incident - Malicious code in postinstall scripts
  • event-stream attack - Long-running exfiltration via lifecycle scripts
  • Dependency confusion - Attackers publishing malicious packages with internal names
This skill covers configuration, tooling, and practices to mitigate these risks across npm, pnpm, and Bun.
npm生态系统是供应链攻击的频繁目标,包括:
  • Shai-Hulud攻击 - 通过被攻陷的包进行蠕虫式传播
  • Nx事件 - postinstall脚本中包含恶意代码
  • event-stream攻击 - 通过生命周期脚本长期窃取数据
  • 依赖混淆 - 攻击者发布带有内部名称的恶意包
本技能涵盖了针对npm、pnpm和Bun的配置、工具及实践,以缓解这些风险。

Secure-by-Default Configuration

默认安全配置

npm (.npmrc)

npm (.npmrc)

Create or update
.npmrc
in your project root or global config (
~/.npmrc
):
ini
undefined
在项目根目录或全局配置(
~/.npmrc
)中创建或更新
.npmrc
ini
undefined

Disable all lifecycle scripts (postinstall, preinstall, etc.)

禁用所有生命周期脚本(postinstall、preinstall等)

ignore-scripts=true
ignore-scripts=true

Block git-based dependencies (git+ssh://, git+https://, etc.)

阻止基于git的依赖项(git+ssh://、git+https://等)

allow-git=none
allow-git=none

Only install packages that have been published for at least 30 days

仅安装发布时间至少30天的包

min-release-age=30

Apply globally:

```bash
npm config set ignore-scripts true
npm config set allow-git none
npm config set min-release-age 30
min-release-age=30

全局应用配置:

```bash
npm config set ignore-scripts true
npm config set allow-git none
npm config set min-release-age 30

pnpm (pnpm-workspace.yaml)

pnpm (pnpm-workspace.yaml)

Create
pnpm-workspace.yaml
in your project root:
yaml
undefined
在项目根目录创建
pnpm-workspace.yaml
yaml
undefined

Block packages newer than 30 days (43200 minutes)

阻止发布时间少于30天的包(43200分钟)

minimumReleaseAge: 43200
minimumReleaseAge: 43200

Reject versions with regressed trust signals (pnpm 10.21+)

拒绝信任信号降级的版本(pnpm 10.21+)

trustPolicy: no-downgrade
trustPolicy: no-downgrade

Allowlist for packages that need build scripts

需要构建脚本的包白名单

allowBuilds: esbuild: true rolldown: true nx@21.6.4 || 21.6.5: true
allowBuilds: esbuild: true rolldown: true nx@21.6.4 || 21.6.5: true

Fail install if unlisted scripts try to run

如果未列入白名单的脚本尝试运行则安装失败

strictDepBuilds: true
strictDepBuilds: true

Block git URLs in dependencies

阻止依赖项中的git URL

blockExoticSubdeps: true
blockExoticSubdeps: true

Optional: ignore trust policy for legacy packages

可选:为遗留包忽略信任策略

trustPolicyIgnoreAfter: 43200
trustPolicyIgnoreAfter: 43200

Optional: exempt specific packages from trust policy

可选:豁免特定包不受信任策略限制

trustPolicyExclude:
  • 'chokidar@4.0.3'
undefined
trustPolicyExclude:
  • 'chokidar@4.0.3'
undefined

Bun (package.json)

Bun (package.json)

Bun disables postinstall scripts by default. To allow specific packages:
json
{
  "trustedDependencies": [
    "esbuild",
    "sharp",
    "fsevents"
  ]
}
Bun默认禁用postinstall脚本。如需允许特定包:
json
{
  "trustedDependencies": [
    "esbuild",
    "sharp",
    "fsevents"
  ]
}

Installation Commands

安装命令

Secure npm install

安全npm安装

bash
undefined
bash
undefined

Install with security flags

带安全标志安装

npm install --ignore-scripts --allow-git=none
npm install --ignore-scripts --allow-git=none

Use npm ci for reproducible installs (CI/CD)

使用npm ci进行可复现安装(CI/CD场景)

npm ci --ignore-scripts
npm ci --ignore-scripts

Install specific package securely

安全安装特定包

npm install lodash --ignore-scripts --allow-git=none
undefined
npm install lodash --ignore-scripts --allow-git=none
undefined

Secure pnpm install

安全pnpm安装

bash
undefined
bash
undefined

Install with workspace config enforced

强制执行工作区配置进行安装

pnpm install
pnpm install

Review blocked packages

查看被阻止的包

pnpm install --loglevel=verbose
pnpm install --loglevel=verbose

Bypass trust policy for specific install (use sparingly)

临时绕过信任策略进行安装(谨慎使用)

pnpm install --ignore-trust-policy
undefined
pnpm install --ignore-trust-policy
undefined

Selective script execution

选择性脚本执行

Use
@lavamoat/allow-scripts
for granular control:
bash
undefined
使用
@lavamoat/allow-scripts
进行精细化控制:
bash
undefined

Install the tool

安装工具

npm install -g @lavamoat/allow-scripts
npm install -g @lavamoat/allow-scripts

Create allowlist

创建白名单

npx allow-scripts setup
npx allow-scripts setup

Edit package.json to add allowlist

编辑package.json添加白名单


Example `package.json`:

```json
{
  "scripts": {
    "prepare": "allow-scripts"
  },
  "lavamoat": {
    "allowScripts": {
      "esbuild": true,
      "core-js": false,
      "nx>@nx/nx-linux-x64-gnu": true
    }
  }
}

示例`package.json`:

```json
{
  "scripts": {
    "prepare": "allow-scripts"
  },
  "lavamoat": {
    "allowScripts": {
      "esbuild": true,
      "core-js": false,
      "nx>@nx/nx-linux-x64-gnu": true
    }
  }
}

Hardening npx Execution

加固npx执行安全

bash
undefined
bash
undefined

Execute without installing package

不安装包直接执行

npx --no-install create-react-app my-app
npx --no-install create-react-app my-app

Use specific version to avoid typosquatting

使用特定版本避免域名抢注攻击

npx create-react-app@5.0.1 my-app
npx create-react-app@5.0.1 my-app

Verify package before running

运行前验证包

npm view create-react-app npx create-react-app my-app
npm view create-react-app npx create-react-app my-app

Use with security wrappers (see below)

配合安全包装器使用(见下文)

undefined
undefined

Security Tooling Integration

安全工具集成

npq - Package quality and security checks

npq - 包质量与安全检查

bash
undefined
bash
undefined

Install globally

全局安装

npm install -g npq
npm install -g npq

Use instead of npm install

替代npm install使用

npq install lodash
npq install lodash

Check package before installing

安装前检查包

npq check lodash
undefined
npq check lodash
undefined

Socket Firewall (sfw)

Socket Firewall (sfw)

bash
undefined
bash
undefined

Install globally

全局安装

npm install -g @socketsecurity/cli
npm install -g @socketsecurity/cli

Protect npm install

保护npm安装

npx @socketsecurity/cli npm install
npx @socketsecurity/cli npm install

Audit project dependencies

审计项目依赖项

npx @socketsecurity/cli audit
npx @socketsecurity/cli audit

Check specific package

检查特定包

npx @socketsecurity/cli info lodash
undefined
npx @socketsecurity/cli info lodash
undefined

Snyk - Vulnerability scanning

Snyk - 漏洞扫描

bash
undefined
bash
undefined

Install CLI

安装CLI

npm install -g snyk
npm install -g snyk

Authenticate

认证

snyk auth
snyk auth

Test for vulnerabilities

测试漏洞

snyk test
snyk test

Monitor project

监控项目

snyk monitor
snyk monitor

Check for supply chain issues

检查供应链问题

snyk code test
undefined
snyk code test
undefined

Automated Dependency Updates with Cooldown

带冷却期的自动化依赖更新

Renovate Bot (renovate.json)

Renovate Bot (renovate.json)

json
{
  "extends": ["config:base"],
  "minimumReleaseAge": "30 days",
  "stabilityDays": 30,
  "prCreation": "not-pending",
  "packageRules": [
    {
      "matchUpdateTypes": ["major"],
      "minimumReleaseAge": "60 days"
    }
  ]
}
json
{
  "extends": ["config:base"],
  "minimumReleaseAge": "30 days",
  "stabilityDays": 30,
  "prCreation": "not-pending",
  "packageRules": [
    {
      "matchUpdateTypes": ["major"],
      "minimumReleaseAge": "60 days"
    }
  ]
}

Dependabot (.github/dependabot.yml)

Dependabot (.github/dependabot.yml)

yaml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
    # Dependabot doesn't support minimumReleaseAge natively
    # Use branch protection rules and manual delay
    open-pull-requests-limit: 10
    labels:
      - "dependencies"
      - "security"
Use GitHub Actions to enforce cooldown:
yaml
name: Dependency Cooldown Check
on:
  pull_request:
    paths:
      - 'package.json'
      - 'package-lock.json'

jobs:
  check-release-age:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Check package age
        run: |
          # Custom script to verify package age
          node scripts/check-package-age.js
yaml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
    # Dependabot原生不支持minimumReleaseAge
    # 使用分支保护规则和手动延迟
    open-pull-requests-limit: 10
    labels:
      - "dependencies"
      - "security"
使用GitHub Actions强制执行冷却期:
yaml
name: Dependency Cooldown Check
on:
  pull_request:
    paths:
      - 'package.json'
      - 'package-lock.json'

jobs:
  check-release-age:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Check package age
        run: |
          # 用于验证包发布时间的自定义脚本
          node scripts/check-package-age.js

Lockfile Security

锁文件安全

Prevent lockfile injection

防止锁文件注入

bash
undefined
bash
undefined

Review lockfile changes in PRs

在PR中查看锁文件变更

git diff package-lock.json
git diff package-lock.json

Verify integrity

验证完整性

npm audit
npm audit

Regenerate if suspicious

若发现可疑则重新生成

rm package-lock.json npm install --ignore-scripts
undefined
rm package-lock.json npm install --ignore-scripts
undefined

.gitattributes protection

.gitattributes防护

undefined
undefined

Prevent merge conflicts from hiding malicious changes

防止合并冲突隐藏恶意变更

package-lock.json merge=binary pnpm-lock.yaml merge=binary
undefined
package-lock.json merge=binary pnpm-lock.yaml merge=binary
undefined

Package Health Assessment

包健康评估

Snyk Advisor lookup

Snyk Advisor查询

bash
undefined
bash
undefined

Check package health

检查包健康状况

CLI check

CLI检查

npx snyk-advisor lodash
undefined
npx snyk-advisor lodash
undefined

Manual verification checklist

手动验证清单

javascript
// check-package-health.js
const fetch = require('node-fetch');

async function checkPackage(packageName) {
  const response = await fetch(`https://registry.npmjs.org/${packageName}`);
  const data = await response.json();
  
  const latestVersion = data['dist-tags'].latest;
  const versionInfo = data.versions[latestVersion];
  
  console.log(`Package: ${packageName}@${latestVersion}`);
  console.log(`Published: ${versionInfo.time || 'N/A'}`);
  console.log(`Maintainers: ${data.maintainers?.length || 0}`);
  console.log(`License: ${versionInfo.license || 'NONE'}`);
  console.log(`Has scripts: ${!!versionInfo.scripts}`);
  console.log(`Dependencies: ${Object.keys(versionInfo.dependencies || {}).length}`);
  
  // Check provenance (npm 9.5+)
  if (versionInfo.dist?.attestations) {
    console.log('✓ Has provenance attestation');
  } else {
    console.log('⚠ No provenance attestation');
  }
}

checkPackage(process.argv[2]);
Run:
bash
node check-package-health.js lodash
javascript
// check-package-health.js
const fetch = require('node-fetch');

async function checkPackage(packageName) {
  const response = await fetch(`https://registry.npmjs.org/${packageName}`);
  const data = await response.json();
  
  const latestVersion = data['dist-tags'].latest;
  const versionInfo = data.versions[latestVersion];
  
  console.log(`包:${packageName}@${latestVersion}`);
  console.log(`发布时间:${versionInfo.time || 'N/A'}`);
  console.log(`维护者数量:${data.maintainers?.length || 0}`);
  console.log(`许可证:${versionInfo.license || 'NONE'}`);
  console.log(`包含脚本:${!!versionInfo.scripts}`);
  console.log(`依赖项数量:${Object.keys(versionInfo.dependencies || {}).length}`);
  
  // 检查来源认证(npm 9.5+)
  if (versionInfo.dist?.attestations) {
    console.log('✓ 拥有来源认证');
  } else {
    console.log('⚠ 无来源认证');
  }
}

checkPackage(process.argv[2]);
运行:
bash
node check-package-health.js lodash

Preventing Dependency Confusion

防范依赖混淆

.npmrc scoped registries

.npmrc作用域注册表

ini
undefined
ini
undefined

Use private registry for org packages

针对组织包使用私有注册表

@mycompany:registry=https://npm.pkg.github.com //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
@mycompany:registry=https://npm.pkg.github.com //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}

Public packages from npm

公共包从npm获取

undefined
undefined

package.json name scoping

package.json名称作用域

json
{
  "name": "@mycompany/internal-lib",
  "version": "1.0.0",
  "publishConfig": {
    "registry": "https://npm.pkg.github.com"
  }
}
json
{
  "name": "@mycompany/internal-lib",
  "version": "1.0.0",
  "publishConfig": {
    "registry": "https://npm.pkg.github.com"
  }
}

Dev Container Security

开发容器安全

.devcontainer/devcontainer.json

.devcontainer/devcontainer.json

json
{
  "name": "Secure Node.js Dev",
  "image": "mcr.microsoft.com/devcontainers/javascript-node:20",
  "features": {
    "ghcr.io/devcontainers/features/node:1": {
      "version": "20"
    }
  },
  "postCreateCommand": "npm config set ignore-scripts true && npm config set allow-git none",
  "remoteEnv": {
    "NPM_CONFIG_IGNORE_SCRIPTS": "true",
    "NPM_CONFIG_ALLOW_GIT": "none"
  },
  "mounts": [
    "source=${localEnv:HOME}/.npmrc,target=/home/node/.npmrc,type=bind,consistency=cached"
  ]
}
json
{
  "name": "安全Node.js开发环境",
  "image": "mcr.microsoft.com/devcontainers/javascript-node:20",
  "features": {
    "ghcr.io/devcontainers/features/node:1": {
      "version": "20"
    }
  },
  "postCreateCommand": "npm config set ignore-scripts true && npm config set allow-git none",
  "remoteEnv": {
    "NPM_CONFIG_IGNORE_SCRIPTS": "true",
    "NPM_CONFIG_ALLOW_GIT": "none"
  },
  "mounts": [
    "source=${localEnv:HOME}/.npmrc,target=/home/node/.npmrc,type=bind,consistency=cached"
  ]
}

Environment Variable Security

环境变量安全

Use .env.vault instead of plaintext .env

使用.env.vault替代明文.env

bash
undefined
bash
undefined

Install dotenv-vault

安装dotenv-vault

npm install dotenv-vault
npm install dotenv-vault

Encrypt secrets

加密密钥

npx dotenv-vault local build
npx dotenv-vault local build

Generate .env.vault (encrypted)

生成.env.vault(加密文件)

npx dotenv-vault push
npx dotenv-vault push

.gitignore update

更新.gitignore

echo ".env" >> .gitignore echo ".env.*.vault" >> .gitignore
undefined
echo ".env" >> .gitignore echo ".env.*.vault" >> .gitignore
undefined

Access encrypted secrets

访问加密密钥

javascript
// app.js
require('dotenv-vault-core').config();

const apiKey = process.env.API_KEY; // Decrypted at runtime
javascript
// app.js
require('dotenv-vault-core').config();

const apiKey = process.env.API_KEY; // 运行时解密

Maintainer Best Practices

维护者最佳实践

Enable 2FA on npm account

在npm账户启用双因素认证

bash
undefined
bash
undefined

Enable 2FA

启用双因素认证

npm profile enable-2fa auth-and-writes
npm profile enable-2fa auth-and-writes

Verify status

验证状态

npm profile get
undefined
npm profile get
undefined

Publish with provenance (npm 9.5+)

带来源认证发布(npm 9.5+)

bash
undefined
bash
undefined

Publish with provenance attestation

带来源认证发布

npm publish --provenance
npm publish --provenance

Verify in package.json before publishing

发布前在package.json中验证

npm pack --dry-run
undefined
npm pack --dry-run
undefined

GitHub Actions OIDC publishing

GitHub Actions OIDC发布

yaml
undefined
yaml
undefined

.github/workflows/publish.yml

.github/workflows/publish.yml

name: Publish Package on: release: types: [created]
jobs: publish: runs-on: ubuntu-latest permissions: id-token: write contents: read steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' registry-url: 'https://registry.npmjs.org' - run: npm ci --ignore-scripts - run: npm publish --provenance --access public env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
undefined
name: Publish Package on: release: types: [created]
jobs: publish: runs-on: ubuntu-latest permissions: id-token: write contents: read steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' registry-url: 'https://registry.npmjs.org' - run: npm ci --ignore-scripts - run: npm publish --provenance --access public env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
undefined

Troubleshooting

故障排除

Scripts are blocked but package needs them

脚本被阻止但包需要运行

Problem: A legitimate package requires postinstall scripts.
Solution: Use allowlist approach with
@lavamoat/allow-scripts
or pnpm
allowBuilds
:
bash
undefined
问题:合法包需要postinstall脚本。
解决方案:使用
@lavamoat/allow-scripts
的白名单方式或pnpm的
allowBuilds
bash
undefined

pnpm approach

pnpm方式

Add to pnpm-workspace.yaml

添加到pnpm-workspace.yaml

allowBuilds: problematic-package: true
allowBuilds: problematic-package: true

npm/yarn approach with lavamoat

npm/yarn配合lavamoat的方式

npm install @lavamoat/allow-scripts npx allow-scripts setup
npm install @lavamoat/allow-scripts npx allow-scripts setup

Edit package.json lavamoat.allowScripts section

编辑package.json中的lavamoat.allowScripts部分

undefined
undefined

Git dependency is required

需要使用git依赖项

Problem: Internal package only available via git URL.
Solution: Use private npm registry instead:
bash
undefined
问题:内部包仅通过git URL可用。
解决方案:改用私有npm注册表:
bash
undefined

Publish to GitHub Packages

发布到GitHub Packages

echo "//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}" >> .npmrc npm publish --registry=https://npm.pkg.github.com
echo "//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}" >> .npmrc npm publish --registry=https://npm.pkg.github.com

Or use Verdaccio for self-hosted registry

或使用Verdaccio搭建自托管注册表

npm install -g verdaccio verdaccio
undefined
npm install -g verdaccio verdaccio
undefined

Cooldown period blocks urgent security fix

冷却期阻止了紧急安全修复

Problem: Security patch released but blocked by min-release-age.
Solution: Temporarily override for specific package:
bash
undefined
问题:安全补丁已发布但被min-release-age阻止。
解决方案:临时为特定包覆盖配置:
bash
undefined

npm - install specific version without min-release-age

npm - 安装特定版本并忽略min-release-age

npm install package@1.2.3 --no-min-release-age
npm install package@1.2.3 --no-min-release-age

pnpm - add to trustPolicyExclude

pnpm - 添加到trustPolicyExclude

In pnpm-workspace.yaml:

在pnpm-workspace.yaml中:

trustPolicyExclude:
  • 'package@1.2.3'
undefined
trustPolicyExclude:
  • 'package@1.2.3'
undefined

Trust policy blocks legitimate package

信任策略阻止了合法包

Problem: pnpm trust policy rejects package downgrade.
Solution: Investigate first, then exempt if safe:
bash
undefined
问题:pnpm信任策略拒绝了包降级。
解决方案:先调查原因,确认安全后豁免:
bash
undefined

Check what changed

检查变更内容

npm view package@version dist.integrity npm view package@old-version dist.integrity
npm view package@version dist.integrity npm view package@old-version dist.integrity

If legitimate, exempt in pnpm-workspace.yaml

若合法,在pnpm-workspace.yaml中豁免

trustPolicyExclude:
  • 'package@version'
undefined
trustPolicyExclude:
  • 'package@version'
undefined

False positive from security scanner

安全扫描器误报

Problem: Snyk/Socket reports issue in vetted package.
Solution: Create exceptions with justification:
yaml
undefined
问题:Snyk/Socket在已审核的包中报告问题。
解决方案:创建带理由的例外规则:
yaml
undefined

.snyk

.snyk

ignore: SNYK-JS-LODASH-12345: - lodash: reason: 'Prototype pollution not exploitable in our use case' expires: '2024-12-31'
undefined
ignore: SNYK-JS-LODASH-12345: - lodash: reason: '原型污染在我们的使用场景中无法被利用' expires: '2024-12-31'
undefined

CI/CD Integration

CI/CD集成

GitHub Actions security workflow

GitHub Actions安全工作流

yaml
name: Security Audit
on: [push, pull_request]

jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      
      - name: Install with security flags
        run: npm ci --ignore-scripts
      
      - name: Audit dependencies
        run: npm audit --audit-level=moderate
      
      - name: Check with Socket
        run: npx @socketsecurity/cli audit
        env:
          SOCKET_SECURITY_API_KEY: ${{ secrets.SOCKET_API_KEY }}
      
      - name: Snyk security scan
        run: npx snyk test --severity-threshold=high
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
yaml
name: Security Audit
on: [push, pull_request]

jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      
      - name: 带安全标志安装
        run: npm ci --ignore-scripts
      
      - name: 审计依赖项
        run: npm audit --audit-level=moderate
      
      - name: 使用Socket检查
        run: npx @socketsecurity/cli audit
        env:
          SOCKET_SECURITY_API_KEY: ${{ secrets.SOCKET_API_KEY }}
      
      - name: Snyk安全扫描
        run: npx snyk test --severity-threshold=high
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

Additional Resources

额外资源