npm-security-best-practices
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesenpm 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 in your project root or global config ():
.npmrc~/.npmrcini
undefined在项目根目录或全局配置()中创建或更新:
~/.npmrc.npmrcini
undefinedDisable 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 30min-release-age=30
全局应用配置:
```bash
npm config set ignore-scripts true
npm config set allow-git none
npm config set min-release-age 30pnpm (pnpm-workspace.yaml)
pnpm (pnpm-workspace.yaml)
Create in your project root:
pnpm-workspace.yamlyaml
undefined在项目根目录创建:
pnpm-workspace.yamlyaml
undefinedBlock 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'
undefinedtrustPolicyExclude:
- 'chokidar@4.0.3'
undefinedBun (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
undefinedbash
undefinedInstall 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
undefinednpm install lodash --ignore-scripts --allow-git=none
undefinedSecure pnpm install
安全pnpm安装
bash
undefinedbash
undefinedInstall 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
undefinedpnpm install --ignore-trust-policy
undefinedSelective script execution
选择性脚本执行
Use for granular control:
@lavamoat/allow-scriptsbash
undefined使用进行精细化控制:
@lavamoat/allow-scriptsbash
undefinedInstall 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
undefinedbash
undefinedExecute 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)
配合安全包装器使用(见下文)
undefinedundefinedSecurity Tooling Integration
安全工具集成
npq - Package quality and security checks
npq - 包质量与安全检查
bash
undefinedbash
undefinedInstall 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
undefinednpq check lodash
undefinedSocket Firewall (sfw)
Socket Firewall (sfw)
bash
undefinedbash
undefinedInstall 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
undefinednpx @socketsecurity/cli info lodash
undefinedSnyk - Vulnerability scanning
Snyk - 漏洞扫描
bash
undefinedbash
undefinedInstall 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
undefinedsnyk code test
undefinedAutomated 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.jsyaml
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.jsLockfile Security
锁文件安全
Prevent lockfile injection
防止锁文件注入
bash
undefinedbash
undefinedReview 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
undefinedrm package-lock.json
npm install --ignore-scripts
undefined.gitattributes protection
.gitattributes防护
undefinedundefinedPrevent merge conflicts from hiding malicious changes
防止合并冲突隐藏恶意变更
package-lock.json merge=binary
pnpm-lock.yaml merge=binary
undefinedpackage-lock.json merge=binary
pnpm-lock.yaml merge=binary
undefinedPackage Health Assessment
包健康评估
Snyk Advisor lookup
Snyk Advisor查询
bash
undefinedbash
undefinedCheck package health
检查包健康状况
CLI check
CLI检查
npx snyk-advisor lodash
undefinednpx snyk-advisor lodash
undefinedManual 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 lodashjavascript
// 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 lodashPreventing Dependency Confusion
防范依赖混淆
.npmrc scoped registries
.npmrc作用域注册表
ini
undefinedini
undefinedUse 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获取
registry=https://registry.npmjs.org/
undefinedregistry=https://registry.npmjs.org/
undefinedpackage.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
undefinedbash
undefinedInstall 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
undefinedecho ".env" >> .gitignore
echo ".env.*.vault" >> .gitignore
undefinedAccess encrypted secrets
访问加密密钥
javascript
// app.js
require('dotenv-vault-core').config();
const apiKey = process.env.API_KEY; // Decrypted at runtimejavascript
// app.js
require('dotenv-vault-core').config();
const apiKey = process.env.API_KEY; // 运行时解密Maintainer Best Practices
维护者最佳实践
Enable 2FA on npm account
在npm账户启用双因素认证
bash
undefinedbash
undefinedEnable 2FA
启用双因素认证
npm profile enable-2fa auth-and-writes
npm profile enable-2fa auth-and-writes
Verify status
验证状态
npm profile get
undefinednpm profile get
undefinedPublish with provenance (npm 9.5+)
带来源认证发布(npm 9.5+)
bash
undefinedbash
undefinedPublish with provenance attestation
带来源认证发布
npm publish --provenance
npm publish --provenance
Verify in package.json before publishing
发布前在package.json中验证
npm pack --dry-run
undefinednpm pack --dry-run
undefinedGitHub Actions OIDC publishing
GitHub Actions OIDC发布
yaml
undefinedyaml
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 }}
undefinedname: 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 }}
undefinedTroubleshooting
故障排除
Scripts are blocked but package needs them
脚本被阻止但包需要运行
Problem: A legitimate package requires postinstall scripts.
Solution: Use allowlist approach with or pnpm :
@lavamoat/allow-scriptsallowBuildsbash
undefined问题:合法包需要postinstall脚本。
解决方案:使用的白名单方式或pnpm的:
@lavamoat/allow-scriptsallowBuildsbash
undefinedpnpm 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部分
undefinedundefinedGit dependency is required
需要使用git依赖项
Problem: Internal package only available via git URL.
Solution: Use private npm registry instead:
bash
undefined问题:内部包仅通过git URL可用。
解决方案:改用私有npm注册表:
bash
undefinedPublish 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
undefinednpm install -g verdaccio
verdaccio
undefinedCooldown 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
undefinednpm - 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'
undefinedtrustPolicyExclude:
- 'package@1.2.3'
undefinedTrust policy blocks legitimate package
信任策略阻止了合法包
Problem: pnpm trust policy rejects package downgrade.
Solution: Investigate first, then exempt if safe:
bash
undefined问题:pnpm信任策略拒绝了包降级。
解决方案:先调查原因,确认安全后豁免:
bash
undefinedCheck 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'
undefinedtrustPolicyExclude:
- 'package@version'
undefinedFalse 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'
undefinedignore:
SNYK-JS-LODASH-12345:
- lodash:
reason: '原型污染在我们的使用场景中无法被利用'
expires: '2024-12-31'
undefinedCI/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 }}