Package managers make development faster, but they also let other people's code run on developer machines, build agents, and production images. A trusted package can become dangerous if a maintainer account is compromised, a malicious dependency is added, or install scripts run unexpected code.
The 2026 Axios npm compromise showed the risk clearly. A popular package briefly shipped malicious versions that added a fake dependency. The dependency ran during installation and downloaded malware, even though the normal application code did not need to import it.
For Node.js projects, prefer pnpm because it is fast, strict, and avoids some common dependency surprises. Learn more on Do you know the best package manager for Node?.
Lockfiles are the first line of defence. They make installs predictable and stop a fresh install from silently resolving to a newly published malicious version.
pnpm-lock.yaml, package-lock.json, yarn.lock, or packages.lock.jsonpnpm install --frozen-lockfile in CI instead of updating the lockfile during installLearn more on Do you properly maintain your dependencies?.
Most malicious package releases are found quickly, but only after early adopters have already installed them. A short cooling-off window gives the community, registry maintainers, scanners, and security teams time to spot bad releases before they reach your builds.
Use a 2-week minimum release age by default. Install newer versions only when the team explicitly needs them and has reviewed the package, release notes, ownership, and lockfile changes.
# pnpm-workspace.yamlminimumReleaseAge: 0
❌ Figure: Bad example - Allowing brand-new package versions into builds immediately
# pnpm-workspace.yamlminimumReleaseAge: 20160
✅ Figure: Good example - Waiting 2 weeks before allowing newly released package versions
minimumReleaseAge is configured in pnpm-workspace.yaml and is measured in minutes. If a package truly needs to bypass the window, document why and use the smallest package-name exception possible:
minimumReleaseAge: 20160minimumReleaseAgeExclude:- "@myorg/internal-package"
Install scripts such as postinstall can run before anyone opens the package. That makes them useful for attackers.
pnpm install --no-frozen-lockfile
❌ Figure: Bad example - Installing fresh dependencies without reviewing what changed
pnpm install --frozen-lockfilepnpm audit
✅ Figure: Good example - Reproducing the locked dependency tree and checking known vulnerabilities
For sensitive environments, consider blocking install scripts by default and allowing them only when needed:
pnpm install --frozen-lockfile --ignore-scripts
This can break packages that legitimately compile native modules or download binaries, so use it where the team can test the impact.
With pnpm, approve dependency build scripts deliberately instead of allowing every package to run install-time code by default. Commit the approved list so changes are reviewed in pull requests:
onlyBuiltDependencies:- "@swc/core"- esbuild- sharp
This allows only those packages to run build scripts during installation. Treat the approval list like code: review it in pull requests and remove approvals that are no longer needed.
Use pnpm approve-builds when you want pnpm to guide you through updating this config.
npm audit and Dependabot are useful, but they mostly focus on known vulnerabilities. Supply chain attacks often involve new packages that have no CVE yet.
Add tools that look for suspicious package behaviour, such as:
Tools such as Socket, Snyk, Sonatype, and GitHub Advanced Security can help depending on your stack and budget.
Learn more on Do you monitor your application for vulnerabilities?.
Package provenance is a receipt for how a package was built. It can show the source repository, build workflow, and publishing process behind a package version.
Provenance does not prove the code is safe, but it helps answer an important question: "Did this package really come from the project it claims to come from?"
For important dependencies, prefer packages that publish provenance or clear build information. Be more cautious when a critical package has no visible link between the source code and the published package.
Automatic dependency PRs are good, but automatic merging is risky for high-impact packages. A malicious package can pass normal unit tests while stealing secrets from the machine running the install.
For important applications:
Learn more on Do you know what the different symbols mean for npm version?.
Assume dependency installation can execute code. Build agents should have the minimum access needed to build and test.
permissions: write-all
❌ Figure: Bad example - Giving every workflow broad write access
permissions:contents: read
✅ Figure: Good example - Starting with read-only access and adding only the permissions the workflow needs
Learn more on Do you store your GitHub secrets in Azure KeyVault?.
If a malicious package may have run, treat the machine as compromised. Removing the package is not enough.
Learn more on Do you record incidents and perform root cause analysis with clear actions?.
Record package-specific decisions using Do you keep a package audit log?.