
The JS ecosystem has been seeing a wave of supply-chain attacks with a consistent pattern: an attacker hijacks a maintainer account or typosquats a popular package, publishes a malicious version, and exploits the fact that npm install runs postinstall scripts with full user privileges. These attacks are typically caught within 24–48 hours — but that window is enough to do damage.
Two things can close most of the gap: a repo-level .npmrc that disables lifecycle scripts and delays installs of freshly published packages, and Safe Chain — an open-source proxy that checks every package against a real-time malware feed before anything touches disk.
Layered defense
The strongest setup treats each environment as a separate layer:
- Local dev: Safe Chain. Catches malware before download.
.npmrcin the repo. Enforced by the package manager, applies to every install everywhere.- CI (GitHub Actions, etc.). The actual enforcement gate before code reaches production.
- Deployment platform. By this point, code should already be vetted.
1. Local developer setup
Install the Safe Chain wrapper on your dev machine following the official installation steps. Add a global config file to override the default 48hrs to 72hrs.
Safe-chain config — ~/.safe-chain/config.json:
{
"minimumPackageAgeHours": 72
}
Always pin the version rather than tracking latest — tracking latest on a security tool is its own supply-chain risk. Bump it deliberately during security review windows.
2. Repo-level .npmrc
Commit this at the repo root, and make sure the lockfile is also committed and required — without it, min-release-age and exact version pinning are meaningless.
ignore-scripts=true
min-release-age=3
engine-strict=true
If you need lifecycle scripts for packages like esbuild or playwright, use LavaMoat’s @lavamoat/allow-scripts as an explicit allowlist rather than turning ignore-scripts off entirely.
Other package managers
pnpm (>= v10) — add to pnpm-workspace.yaml:
minimumReleaseAge: 10080minimumReleaseAge: 4320 # 72hrs
onlyBuiltDependencies: [] # explicit allowlist for postinstalls
pnpm v10+ disables postinstall scripts by default and lets you opt specific packages in via onlyBuiltDependencies.
Yarn (v2+) — add to .yarnrc.yml:
npmMinimalAgeGate: 3d
enableScripts: false
Bun — add to bunfig.toml:
[install]
minimumReleaseAge = 259200 # 72hrs in seconds
Bun disables postinstall scripts by default and uses trustedDependencies in package.json for the allowlist.
3. CI — the actual enforcement layer
Safe Chain is officially supported on GitHub Actions, Azure Pipelines, CircleCI, Jenkins, Bitbucket Pipelines, and GitLab Pipelines. Canonical configs for all six are in the Safe Chain CI/CD docs. GitHub Actions example:
- name: Install safe-chain
run: curl -fsSL https://github.com/AikidoSec/safe-chain/releases/download/1.4.4/install-safe-chain.sh | sh -s -- --ci
- name: Install dependencies
run: npm ci
env:
SAFE_CHAIN_LOGGING: verbose
A few things worth keeping in mind:
- Pin the installer version, not
latest - Use
npm ci/--frozen-lockfile, nevernpm installin CI (which doesn’t honor the lockfile) - Set
SAFE_CHAIN_LOGGING=verboseso blocks are visible in build logs - Mark the CI workflow as a required status check on the default branch
4. Deployment platforms
Platforms like Vercel, Heroku, and Railway have mixed support for customizing the install command, so Safe Chain at this step is optional defense-in-depth. When code reaches these platforms, it should already be vetted by CI.

Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.