Loading...
Loading...
Expert guidance on securing npm packages, preventing supply chain attacks, and hardening package manager configurations
npx skill4agent add aradotso/security-skills npm-security-best-practicesSkill by ara.so — Security Skills collection.
.npmrc~/.npmrc# Disable all lifecycle scripts (postinstall, preinstall, etc.)
ignore-scripts=true
# Block git-based dependencies (git+ssh://, git+https://, etc.)
allow-git=none
# Only install packages that have been published for at least 30 days
min-release-age=30npm config set ignore-scripts true
npm config set allow-git none
npm config set min-release-age 30pnpm-workspace.yaml# Block packages newer than 30 days (43200 minutes)
minimumReleaseAge: 43200
# Reject versions with regressed trust signals (pnpm 10.21+)
trustPolicy: no-downgrade
# Allowlist for packages that need build scripts
allowBuilds:
esbuild: true
rolldown: true
nx@21.6.4 || 21.6.5: true
# Fail install if unlisted scripts try to run
strictDepBuilds: true
# Block git URLs in dependencies
blockExoticSubdeps: true
# Optional: ignore trust policy for legacy packages
trustPolicyIgnoreAfter: 43200
# Optional: exempt specific packages from trust policy
trustPolicyExclude:
- 'chokidar@4.0.3'{
"trustedDependencies": [
"esbuild",
"sharp",
"fsevents"
]
}# Install with security flags
npm install --ignore-scripts --allow-git=none
# Use npm ci for reproducible installs (CI/CD)
npm ci --ignore-scripts
# Install specific package securely
npm install lodash --ignore-scripts --allow-git=none# Install with workspace config enforced
pnpm install
# Review blocked packages
pnpm install --loglevel=verbose
# Bypass trust policy for specific install (use sparingly)
pnpm install --ignore-trust-policy@lavamoat/allow-scripts# Install the tool
npm install -g @lavamoat/allow-scripts
# Create allowlist
npx allow-scripts setup
# Edit package.json to add allowlistpackage.json{
"scripts": {
"prepare": "allow-scripts"
},
"lavamoat": {
"allowScripts": {
"esbuild": true,
"core-js": false,
"nx>@nx/nx-linux-x64-gnu": true
}
}
}# Execute without installing package
npx --no-install create-react-app my-app
# Use specific version to avoid typosquatting
npx create-react-app@5.0.1 my-app
# Verify package before running
npm view create-react-app
npx create-react-app my-app
# Use with security wrappers (see below)# Install globally
npm install -g npq
# Use instead of npm install
npq install lodash
# Check package before installing
npq check lodash# Install globally
npm install -g @socketsecurity/cli
# Protect npm install
npx @socketsecurity/cli npm install
# Audit project dependencies
npx @socketsecurity/cli audit
# Check specific package
npx @socketsecurity/cli info lodash# Install CLI
npm install -g snyk
# Authenticate
snyk auth
# Test for vulnerabilities
snyk test
# Monitor project
snyk monitor
# Check for supply chain issues
snyk code test{
"extends": ["config:base"],
"minimumReleaseAge": "30 days",
"stabilityDays": 30,
"prCreation": "not-pending",
"packageRules": [
{
"matchUpdateTypes": ["major"],
"minimumReleaseAge": "60 days"
}
]
}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"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# Review lockfile changes in PRs
git diff package-lock.json
# Verify integrity
npm audit
# Regenerate if suspicious
rm package-lock.json
npm install --ignore-scripts# Prevent merge conflicts from hiding malicious changes
package-lock.json merge=binary
pnpm-lock.yaml merge=binary# Check package health
curl https://snyk.io/advisor/npm-package/lodash | jq
# CLI check
npx snyk-advisor lodash// 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]);node check-package-health.js lodash# Use private registry for org packages
@mycompany:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
# Public packages from npm
registry=https://registry.npmjs.org/{
"name": "@mycompany/internal-lib",
"version": "1.0.0",
"publishConfig": {
"registry": "https://npm.pkg.github.com"
}
}{
"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"
]
}# Install dotenv-vault
npm install dotenv-vault
# Encrypt secrets
npx dotenv-vault local build
# Generate .env.vault (encrypted)
npx dotenv-vault push
# .gitignore update
echo ".env" >> .gitignore
echo ".env.*.vault" >> .gitignore// app.js
require('dotenv-vault-core').config();
const apiKey = process.env.API_KEY; // Decrypted at runtime# Enable 2FA
npm profile enable-2fa auth-and-writes
# Verify status
npm profile get# Publish with provenance attestation
npm publish --provenance
# Verify in package.json before publishing
npm pack --dry-run# .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 }}@lavamoat/allow-scriptsallowBuilds# pnpm approach
# Add to pnpm-workspace.yaml
allowBuilds:
problematic-package: true
# npm/yarn approach with lavamoat
npm install @lavamoat/allow-scripts
npx allow-scripts setup
# Edit package.json lavamoat.allowScripts section# Publish to GitHub Packages
echo "//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}" >> .npmrc
npm publish --registry=https://npm.pkg.github.com
# Or use Verdaccio for self-hosted registry
npm install -g verdaccio
verdaccio# npm - install specific version without min-release-age
npm install package@1.2.3 --no-min-release-age
# pnpm - add to trustPolicyExclude
# In pnpm-workspace.yaml:
trustPolicyExclude:
- 'package@1.2.3'# Check what changed
npm view package@version dist.integrity
npm view package@old-version dist.integrity
# If legitimate, exempt in pnpm-workspace.yaml
trustPolicyExclude:
- 'package@version'# .snyk
ignore:
SNYK-JS-LODASH-12345:
- lodash:
reason: 'Prototype pollution not exploitable in our use case'
expires: '2024-12-31'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 }}