Join our Newsletter — 33% off our NHI Course
Home› Guides› Node.js Security Guide: npm Supply Chain, Runtime Risks…
Guide Application Security

Node.js Security Guide: npm Supply Chain, Runtime Risks and Secrets

PDF for newsletter subscribers · free to read online

← All guides
By Lalit Choda, NHI Mgmt Group Updated 24 October 2026 17 min read
On this page

A typical Node.js application runs hundreds of third-party packages with the same file system, network and environment access as its own code, and any one of them can run a script the moment it is installed. The 2025 Shai-Hulud worm showed what that means at scale: stolen npm publishing tokens and install scripts spread a secret stealer from package to package and from developer laptops to CI runners. This guide covers the npm supply chain (lockfiles, install scripts, provenance, trusted publishing and the 2025 token changes), runtime risks in Node.js itself (prototype pollution, the permission model, process privilege and container hardening) and how to keep secrets out of reach of dependencies. It is written for JavaScript and TypeScript developers, platform and DevSecOps engineers, and security teams reviewing Node.js services and build pipelines.

Key takeaways

  • Install from the lockfile with scripts off. npm ci --ignore-scripts installs exactly what package-lock.json records and stops preinstall and postinstall code from running; allow scripts only for the few packages that need them.
  • Publishing tokens are the main target. npm revoked all classic tokens on 9 December 2025; use trusted publishing (OIDC) from CI, and where a token is unavoidable use a short-lived granular token.
  • Provenance proves origin, not safety. npm audit signatures verifies registry signatures and provenance attestations, which helps spot releases that did not come from the expected repository and workflow.
  • The permission model is a seat belt. --permission restricts file system, child process, worker and addon access for trusted code; the Node.js documentation is explicit that it does not stop malicious code.
  • Keep secrets away from installs. Most Shai-Hulud infections ran on CI runners, where tokens sat in the environment of the install step; give credentials only to the step that needs them.

The Node.js threat model

Node.js trusts all code it is asked to run, as its own security policy and permission documentation state. Risk therefore concentrates where untrusted code or data enters. ATT&CK IDs are from ATT&CK v20.

AreaTypical failureATT&CK techniquePrimary control
DependenciesHijacked maintainer account publishes a malicious version; typosquat; lockfile not usedT1195.001 Compromise Software Dependencies and Development Toolsnpm ci from a reviewed lockfile, release cooldown, private registry proxy
Install-time executionpreinstall or postinstall script steals tokens from .npmrc and environment variablesT1059.007 Command and Scripting Interpreter: JavaScriptignore-scripts by default, allowlist for packages that need scripts
PublishingStolen npm token republishes the victim's packagesT1195.002 Compromise Software Supply ChainTrusted publishing, "disallow tokens" setting, provenance
RuntimePrototype pollution, command injection, unsafe deserialisation, exposed inspectorT1059.007Input validation, safe object handling, permission model, no inspector in production
SecretsTokens in .npmrc, .env or CI environment read by dependency codeT1552.001 Credentials In FilesSecrets manager, per-step scoping, short-lived credentials

For the cross-ecosystem view see the Software Supply Chain Security Guide and the Malicious Package and Dependency Confusion Guide.

Lockfiles, install scripts and dependency intake

Lockfiles and npm ci

  • Commit the lockfile: package-lock.json (or pnpm-lock.yaml, yarn.lock, bun.lock) pins every direct and transitive dependency with an integrity hash. Pinning only direct dependencies in package.json still lets transitive packages move to new, possibly malicious versions, a risk the Node.js security best practices page calls out.
  • Use npm ci in automation: npm ci requires a lockfile, fails if it does not match package.json instead of rewriting it, deletes any existing node_modules and never writes to package.json or the lockfile. npm install in CI can silently resolve new versions.
  • Review lockfile diffs: a pull request that changes resolved URLs to an unexpected registry or host, or adds many transitive packages, deserves the same review as code.
  • Cooldown on new releases: npm 11.10.0 and later support min-release-age in .npmrc (a number of days), so installs only take versions published more than that many days ago. Many malicious versions are detected and removed within hours or days. Override per command with --min-release-age=0 for an urgent security fix.

Install scripts

npm runs preinstall, install and postinstall for packages being installed, and prepare on a local npm install with no arguments. If a package has a binding.gyp file and no install script of its own, npm runs node-gyp rebuild by default. npm's own documentation advises package authors to avoid install scripts except for compilation that must happen on the target machine.

  • Default off: set ignore-scripts=true in the project or user .npmrc, or run npm ci --ignore-scripts. Scripts you invoke directly, such as npm test, still run, but their pre and post hooks do not.
  • Allowlist the exceptions: current npm 11 releases add an allowScripts policy for the project, configured in package.json or .npmrc, and an allow-scripts option for one-off contexts such as npx and global installs. dangerously-allow-all-scripts bypasses the policy and npm describes its use as strongly discouraged. ignore-scripts takes precedence over both.

Where packages come from

  • Scoped internal packages: publish internal packages under an organisation scope and map that scope to your private registry in .npmrc, so an unscoped public package with the same name cannot be substituted.

Intake rules that apply across ecosystems are in the Open Source Security Guide.

Publishing: trusted publishing, provenance and the 2025 token changes

What changed in late 2025

After the first Shai-Hulud wave, GitHub changed npm authentication in stages, according to its changelog posts:

  • 5 November 2025: new classic tokens could no longer be created on the website, CLI or API. New granular tokens with write permission require 2FA by default, with an optional "Bypass 2FA" setting for CI that is unchecked by default. Write-capable granular tokens are capped at a 90-day maximum lifetime, and existing ones due to expire after 3 February 2026 were moved to that date.
  • 9 December 2025: all classic tokens were permanently revoked (the November post had given 19 November as the revocation date; the December changelog records 9 December). npm login now issues a two-hour session token rather than a long-lived one, 2FA is enforced for publishing during a session, and granular tokens can be created, listed and revoked from the CLI with npm token.

Any remaining stored npm tokens now expire within 90 days at most; each needs an owner, a rotation process and the narrowest package scope.

Trusted publishing

npm trusted publishing lets a CI workflow publish using OpenID Connect instead of a stored token. You register the repository and workflow as a trusted publisher for the package; at publish time the npm CLI detects the OIDC environment and uses it before falling back to tokens. Supported providers are GitHub Actions (GitHub-hosted runners), GitLab CI/CD (GitLab.com shared runners) and CircleCI cloud; self-hosted runners are not yet supported. It requires npm CLI 11.5.1 or later and Node.js 22.14.0 or later.

  • Close the token path: once trusted publishing works, set the package's publishing access to "Require two-factor authentication and disallow tokens". Trusted publishers keep working because they do not use tokens.
  • Add human approval: npm also supports allowing only staged publishing (npm stage publish), so CI publishes wait for maintainer approval.
  • Protect the workflow: the publishing workflow is now the credential. Restrict who can change it, avoid running pull-request code in it, and require reviewers on its environment. See the GitHub and GitHub Actions Security Guide.

Provenance

npm provenance attestations link a published version to its source repository and build, signed through the public Sigstore service and recorded in a transparency log. They are generated automatically for public packages published by trusted publishing from public repositories on GitHub Actions or GitLab CI/CD (not CircleCI), or manually with npm publish --provenance and id-token: write on a cloud-hosted runner. The repository field in package.json must match the source exactly. Consumers run npm audit signatures to verify registry signatures and attestations for installed packages. npm is clear that provenance does not guarantee a package is free of malicious code; it gives a verifiable link to review.

Publishing methodCredential lifetimeMain riskUse when
Classic tokenRevoked 9 December 2025Not availableNever
Granular token with write accessUp to 90 daysTheft from CI secrets, .npmrc or developer machinesSelf-hosted runners or unsupported CI; scope to named packages
npm login sessionTwo hoursPhishing of the maintainer accountManual publishing with phishing-resistant 2FA
Trusted publishing (OIDC)Per publish, no stored secretCompromise of the publishing workflow itselfDefault for supported cloud CI
Trusted publishing plus "disallow tokens" and staged publishPer publishNeeds workflow compromise and maintainer approvalWidely used packages

Publishing tokens and CI identities are non-human identities; manage them with the same inventory and ownership rules. See the Secrets Management Guide.

Runtime risks in Node.js

Prototype pollution

Prototype pollution (CWE-1321) happens when untrusted input containing keys such as __proto__, constructor or prototype is merged or assigned into an object, adding properties to Object.prototype that every object then inherits. Depending on how the application uses those properties, the effect ranges from logic bypass and denial of service to remote code execution through other gadgets in the code. Mitigations from the Node.js best practices page and the OWASP cheat sheet:

  • Avoid unsafe recursive merges: do not deep-merge request bodies into configuration or option objects; validate input against a JSON Schema first and drop unknown keys.
  • Use structures without a prototype: Map and Set for keyed data, or Object.create(null) for plain dictionaries.
  • Check own properties: use Object.hasOwn(obj, key) rather than obj[key] truthiness for security decisions.
  • Defence in depth: --disable-proto=delete removes Object.prototype.__proto__ (or =throw makes access throw ERR_PROTO_ACCESS); OWASP notes this does not stop pollution through constructor.prototype. Freezing prototypes with Object.freeze also helps but is shallow and can break libraries that modify built-ins.
// Safer handling of untrusted keys
const settings = new Map();
for (const [k, v] of Object.entries(validatedInput)) settings.set(k, v);

const lookup = Object.create(null);   // no inherited properties
if (Object.hasOwn(lookup, key)) { /* ... */ }

Other runtime classes

  • Command injection: prefer child_process.execFile or spawn with an argument array over exec with a built string, which runs through a shell.
  • Code from strings: eval, new Function and the vm module are not a security boundary for untrusted code. See the Insecure Deserialisation Guide for serialisation libraries that revive functions.
  • Inspector exposure: never run --inspect in production; the debugger can be reached through DNS rebinding from a malicious web page.
  • HTTP hardening: set headersTimeout, requestTimeout and keepAliveTimeout on http.Server, handle socket errors, do not enable insecureHTTPParser, and compare secrets with crypto.timingSafeEqual.

Language-neutral rules are in the Secure Coding Guide and the OWASP Top 10 Guide.

The permission model, process privilege and containers

The Node.js permission model

Starting Node.js with --permission denies access to restricted resources unless a flag grants it. The model and the --allow-fs-read and --allow-fs-write flags became stable in Node.js 23.5.0 and 22.13.0; the other flags are marked as in active development.

FlagGrantsWithout it
--allow-fs-read=<path>File system reads under the path (repeat the flag for more paths)ERR_ACCESS_DENIED
--allow-fs-write=<path>File system writes under the pathERR_ACCESS_DENIED
--allow-child-processSpawning child processesERR_ACCESS_DENIED
--allow-workerWorker threadsAccess denied
--allow-addonsNative addonsERR_DLOPEN_DISABLED
--allow-wasiWASI instancesAccess denied
--allow-netNetwork access (added in Node.js 25.0.0)Access denied

Code can check its own grants with process.permission.has(scope[, reference]). The Node.js documentation describes the model as a "seat belt" that stops trusted code from unintentionally using resources it was not granted, and states that malicious code can bypass it. Use it to limit the blast radius of mistakes and to document what a service needs, not as a sandbox for untrusted packages. For that, use process and container isolation.

node --permission --allow-fs-read=/app --allow-fs-write=/app/tmp server.js

Process privilege and containers

  • Do not run as root: run the Node.js process as an unprivileged user that owns nothing except what it must write, so a compromised dependency cannot modify node_modules or the application code.
  • Separate build and runtime: use a multi-stage image. Install with dev dependencies and build in one stage; copy only the production node_modules and built output into a minimal runtime image without npm credentials or .npmrc.
  • Read-only file system: mount the application directory read-only and give a writable volume only for temporary files.
  • Egress control: restrict outbound network access from the container to the services it calls; exfiltration by malicious packages needs outbound connections.
  • Supported releases: run an LTS line that still receives security releases. As of October 2026 the Node.js releases page lists 24.x (Krypton) and 22.x (Jod) as LTS, with 20.x end-of-life.

See the Container Security Guide and the Container Image Security Guide.

Secrets in Node.js projects

  • .npmrc tokens: an _authToken line in a user or project .npmrc is readable by every install script. In the eslint-scope npm compromise, a malicious postinstall script read installers' .npmrc files and sent the tokens to the attacker. Reference an environment variable instead (${NPM_TOKEN}) and set it only in the step that publishes.
  • Environment variables: process.env is visible to every module. Do not load cloud keys or database passwords into the environment of build or test steps that do not need them; prefer workload identity or a secrets manager client in the running service.
  • .env files: keep them out of version control, images and published packages. Check the files field in package.json or .npmignore, and run npm pack --dry-run to see exactly what a release will contain.
  • Scan artefacts: scan repositories, packed tarballs and images for secrets, not only commits.

See the Top 10 Secrets Management Mistakes.

Lessons from incidents

The Shai-Hulud npm worm is the clearest illustration of these risks. The first wave, reported on 15 September 2025, used stolen npm tokens to republish victims' own packages and repurposed TruffleHog to hunt for further secrets; CISA counted more than 500 affected packages. The second wave from 24 November 2025 added a preinstall script, so code ran on developer machines and CI runners during installation, published stolen secrets to public GitHub repositories and registered a self-hosted runner for persistence. Wiz found 77% of infections on CI/CD runners. One entry point was a misconfigured pull_request_target workflow that exposed a bot token and then an npm publishing token. The lessons map directly to controls in this guide: no stored publishing tokens, scripts off by default in CI, secrets scoped to single steps, fast and complete rotation (many leaked tokens were still valid a week later), and an inventory of who and what can publish each package.

How to implement: a phased rollout

  1. Inventory: list every Node.js service, build pipeline and published package, its Node.js version, lockfile type and publishing method, and every npm token with its owner and expiry.
  2. Observe: run npm audit --audit-level=high --omit=dev, npm audit signatures and an OSV-based scanner in report-only mode for two to four weeks. Record which dependencies have install scripts.
  3. Lockfile and npm ci: make npm ci mandatory in CI and block merges that change package.json without the lockfile.
  4. Scripts off: set ignore-scripts=true in a pilot repository, add the allowlist for packages that need scripts, then roll out by team. Add min-release-age with a short cooldown.
  5. Publishing: move each package to trusted publishing, publish one release, set "disallow tokens", and revoke remaining granular tokens.
  6. Runtime: non-root containers, read-only file systems, egress rules, and --permission with explicit grants for services where the needed paths are well understood.
  7. Enforce: make critical and known-exploited vulnerabilities and unexpected install scripts blocking, with time-limited exceptions.

Testing and rollback: run the permission model first in staging and watch for ERR_ACCESS_DENIED to find missing grants. If ignore-scripts breaks a native module, add that one package to the allowlist rather than turning scripts back on. If a trusted publishing release fails during an incident, issue a granular token scoped to the one package with a one-day expiry, publish, and revoke it.

Decision guidance

  • Small teams: committed lockfile, npm ci --ignore-scripts, trusted publishing, npm audit in CI and a hosted secret scanner cover most of the risk.
  • Large organisations: add a registry proxy with central cooldown and blocking, provenance checks for critical dependencies, a published allowlist of packages permitted to run scripts, and named owners for every publishing identity.

Detection and measurement

  • Monitor: new versions of your packages with no matching tag, workflow run or provenance; new public repositories or self-hosted runners appearing in your GitHub organisation; Node.js or npm processes on runners connecting to unexpected hosts; and any install that runs a script not on the allowlist.
  • Log: CI install and publish logs, npm organisation audit events, and secret-scanner alerts, retained long enough to cover slow-burn exposures.

Metrics:

  1. Percentage of CI builds using npm ci with ignore-scripts.
  2. Number of live npm tokens with write access (target: zero for packages on trusted publishing).
  3. Percentage of your published versions carrying provenance.
  4. Percentage of installed dependencies with verified attestations, from npm audit signatures.
  5. Open critical or known-exploited dependency vulnerabilities and median days to fix.
  6. Median time from a leaked-token alert to revocation.

Framework mapping

Control areaNIST SP 800-53 Rev. 5NIST CSF 2.0Other
Dependency intake, lockfiles, provenanceSR-3 Supply Chain Controls and Processes, SR-11 Component Authenticity, SI-7 Software, Firmware, and Information IntegrityGV.SC-05, PR.PS-06CIS Controls v8.1 Control 16 (16.4, 16.5); ATT&CK T1195.001, T1195.002
Install scripts and permission modelCM-7 Least FunctionalityPR.PS-06ATT&CK T1059.007
Secure coding (prototype pollution, injection)SA-11 Developer Testing and Evaluation, SI-10 Information Input ValidationPR.PS-06ISO/IEC 27001:2022 A.8.28
Vulnerability scanningRA-5 Vulnerability Monitoring and ScanningID.RA-01ISO/IEC 27001:2022 A.8.8
Publishing tokens and secretsIA-5 Authenticator ManagementPR.AA-01ATT&CK T1552.001

Common failure patterns

  • Publishing token in the CI environment of every job: any compromised dependency or workflow step can read it, which is how worms such as Shai-Hulud move from one maintainer's packages to the next.
  • Former maintainers keep publish rights: in the Ledger Connect Kit npm compromise, a former employee whose npm access had not been properly revoked was phished, and that access was used to publish versions carrying a wallet drainer. Review package maintainers and tokens when people leave.
  • Install scripts enabled everywhere: a single malicious postinstall runs with the developer's or runner's full access.
  • Treating the permission model as a sandbox: it constrains trusted code and is documented as bypassable by malicious code.

Practitioner checklist

  • Every repository commits a lockfile and CI uses npm ci (or the pnpm or Yarn equivalent) only.
  • ignore-scripts=true is the default, with a reviewed allowlist for packages that need install scripts.
  • A min-release-age cooldown or proxy quarantine applies to new dependency versions.
  • Internal packages are scoped and the scope maps to the private registry.
  • Every package you publish uses trusted publishing with "disallow tokens" set; any remaining granular token has an owner, a package scope and an expiry.
  • npm audit signatures and a vulnerability scan run in CI.
  • Untrusted input is schema-validated and never deep-merged into objects; dictionaries use Map or Object.create(null).
  • Production runs on a supported LTS release, as a non-root user, with a read-only file system, no inspector and restricted egress.
  • Secrets are injected only into the steps and services that use them and are never present in .npmrc files on disk.

Standards and references

Related NHI Mgmt Group resources: Software Supply Chain Security Guide · Secure Coding Guide · Secrets Management Guide · GitHub and GitHub Actions Security Guide · Python Security Guide

PDF for subscribers

Take this guide with you

Every section, table and checklist in one PDF, with links to the sources and related guides. Subscribe to our weekly NHI & AI Identity Journal and we’ll email you a download link.

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Written and reviewed by Lalit Choda, NHI Mgmt Group. Last updated 24 October 2026.
NHI Mgmt Group — the #1 independent authority on Non-Human Identity, IAM, and Agentic AI security. nhimg.org

PDF Downloads for Newsletter Subscribers

Get the guide

Enter your details and we’ll email you a download link.

The full guide is free to read online. The PDF is a thank you for subscribing.

Fill in all fields marked * and tick the PDF box to continue.