Milind Daraniya

npm Supply-Chain Attacks in 2026: How to Protect Your Dependencies, GitHub Actions, and Cloud Secrets

Published October 20th, 2026 18 min read

Installing a JavaScript package is usually a routine part of development. We add a dependency, run the installation command, and continue working on the application.

But what happens when a package that we trust is compromised?

A malicious dependency can do much more than introduce a bug into an application. Depending on how it executes and which permissions are available, it may be able to read local files, collect developer credentials, access GitHub repositories, or use cloud credentials available to a build pipeline.

This is not just a theoretical concern.

On August 4, 2026, Microsoft published an investigation into a supply-chain attack called ChainDrop, which affected more than 400 npm packages. The malicious packages used installation scripts to execute code and could steal credentials from developer machines and CI/CD environments. Attackers could then use those credentials to modify additional packages and repositories. (Microsoft Security Research)

GitHub also announced changes to npm Trusted Publishing on October 2, 2026, including the expiration of unvalidated publishing configurations after 48 hours. That change addresses a different supply-chain risk, but it reinforces the importance of securing the entire package-release process. (GitHub Changelog)

For developers maintaining Node.js, React, or Next.js applications, these incidents offer several practical lessons.

What is a software supply-chain attack?

A software supply-chain attack targets something that an application or its development process trusts.

That may be a package, maintainer account, repository, automated build process, release workflow, or developer tool.

For example, a React application might depend on dozens of direct npm packages. Each of those packages can depend on other packages, creating a much larger dependency tree.

A simplified dependency structure could look like this:

My Web Application
        |
        +-- next
        |     |
        |     +-- Package A
        |     |
        |     +-- Package B
        |
        +-- another-library
              |
              +-- Package C
                    |
                    +-- Package D

You may have reviewed the code in your application without ever examining the source code of every dependency.

That is understandable. Modern applications rely heavily on reusable libraries.

The problem appears when an attacker compromises a dependency or the process used to publish it. The malicious code can enter a trusted application through an otherwise ordinary installation.

This is why dependency security is not simply a matter of checking your own source code.

How can a malicious npm package execute code?

One important mechanism is the npm package lifecycle.

A package can define scripts that run during installation. For example, its package.json might contain:

{
  "name": "example-package",
  "version": "1.0.0",
  "scripts": {
    "preinstall": "node setup.js"
  }
}

This is a simplified, illustrative example. An installation script is not automatically malicious. Some legitimate packages use lifecycle scripts to prepare native modules, download required binaries, or perform other setup operations.

However, the mechanism creates an opportunity for abuse.

When npm installs a package containing an applicable lifecycle script, that script may execute during installation. The code can run with the permissions of the installing process.

If installation happens on a developer's laptop, the process might have access to files and credentials belonging to that developer.

If installation happens in a CI/CD environment, it might have access to environment variables, workflow permissions, package-publishing credentials, or cloud credentials available to that job.

The potential impact therefore depends on both the package's behavior and the permissions of the environment where it runs.

What happened in the ChainDrop attack?

Microsoft's investigation describes a malicious npm campaign involving more than 400 packages across multiple publishers.

The malicious releases used installation-time execution and could collect credentials from developer workstations and build environments. The stolen credentials could provide access to npm, GitHub, cloud services, and infrastructure systems. The attack also included mechanisms for propagating malicious package releases and modifying development repositories. (Microsoft Security Research)

The important lesson is that the package itself was only one part of the problem.

Consider the following simplified sequence:

1. An attacker compromises a package or publishing identity.
                 |
                 v
2. A malicious dependency is published.
                 |
                 v
3. A developer or CI runner installs it.
                 |
                 v
4. Installation-time code executes.
                 |
                 v
5. Available credentials may be collected.
                 |
                 v
6. Those credentials may provide access to other systems.
                 |
                 v
7. More packages, repositories, or build artifacts may be affected.

This is a conceptual representation of the threat. Not every incident follows every step.

The key point is that the initial compromise of one dependency can create a much larger problem when developers and build systems have broad access to other resources.

Why npm audit is not enough

Many development teams run:

npm audit

This is useful. It checks dependencies against known security advisories and can identify packages with reported vulnerabilities.

However, npm audit does not prove that every installed package is trustworthy.

There are several reasons for this:

A newly compromised package may not have a published advisory yet. A malicious release may appear before security researchers identify it and publish an alert.

A legitimate package can be compromised. The package name may be familiar and the repository may appear legitimate, while a particular published release has been altered.

A lockfile does not guarantee that the package is benign. It can help reproduce a specific dependency resolution, but it does not independently verify the intention or safety of that code.

Transitive dependencies matter. A direct dependency may pull in another package that receives less attention during code review.

The right approach is to use npm audit as one layer of protection rather than treating a clean audit report as a security certificate.

Step 1: Review dependency changes before installation

When a project changes, review modifications to both package.json and the corresponding lockfile.

For a Git-managed project, start with:

git diff -- package.json package-lock.json

This helps reveal newly added dependencies, version changes, and other modifications to the dependency resolution.

Also inspect the dependency tree:

npm ls --all

This command helps identify which versions of packages are installed and how dependencies are connected.

For a specific package, examine its metadata and published version information:

npm view package-name@1.2.3 time dist.integrity repository.url

Replace the example package name and version with the dependency you are investigating.

Package metadata is useful, but it is not sufficient on its own. When a release looks suspicious, compare it with the official repository, maintainer announcements, release history, and security advisories. For higher-risk dependencies, review the contents of the exact published package in an isolated environment.

Avoid making security decisions based solely on download counts, package stars, or a familiar name.

Step 2: Use reproducible installations correctly

For projects that commit a package lockfile, npm ci is commonly used for clean, reproducible installations:

npm ci

Unlike a normal dependency update, this command installs the dependency versions specified in the lockfile and fails when the manifest and lockfile are inconsistent.

That is helpful for repeatability across developer machines and CI/CD environments.

However, there is an important limitation: npm ci does not make a malicious locked dependency safe. It can reproducibly install the exact package version that an attacker introduced into the dependency tree.

Before running an installation, especially one involving unfamiliar or recently changed dependencies, review the change to the lockfile.

A practical workflow is:

Review dependency changes.

Investigate unexpected packages or version changes.

Update the lockfile intentionally.

Run security and application tests.

Commit the approved dependency tree.

Let CI install the reviewed lockfile consistently.

Where a package is not required by the application, remove it rather than leaving unnecessary dependencies available to future installations.

Step 3: Understand the risk from installation scripts

Some attacks execute during package installation, before an application's ordinary test suite has finished.

For that reason, package installation should be treated as an operation that can execute untrusted code.

An option such as:

npm ci --ignore-scripts

can be useful for controlled investigations or environments where installation scripts are intentionally disabled.

However, it should not be applied blindly to every production project. Some legitimate packages rely on installation scripts, and disabling them can prevent software from building or working correctly.

A better approach is to review which dependencies need lifecycle scripts, investigate unexpected script changes, and test any decision to disable them in an isolated environment.

The important goal is to limit unnecessary execution, not to break legitimate packages without understanding their requirements.

Step 4: Protect GitHub Actions and CI/CD environments

A compromised dependency is significantly more dangerous when it runs inside a build job that has access to deployment credentials.

For example, a build pipeline might have access to:

GitHub repository tokens.

npm publishing credentials.

Cloud-provider credentials.

Database passwords.

Application secrets.

Deployment keys.

If malicious installation code executes inside that job, the impact may extend beyond the application currently being built.

A common design mistake is to give every CI job access to every secret, even when most jobs only need to install dependencies and run tests.

Start by limiting GitHub Actions permissions:

permissions:
  contents: read

This is an example of a minimal repository permission configuration, not a complete workflow. The exact permissions required depend on what the job does.

For instance, a test job generally should not need the same permissions as a deployment job that publishes packages or updates production infrastructure.

Separate those responsibilities.

A safer CI/CD design

A practical design separates the workflow into distinct stages:

Dependency and test stage: Install reviewed dependencies, run automated tests, and build the application without unnecessary production credentials.

Review and approval stage: Verify the source branch, reviewed changes, and any required approvals.

Deployment stage: Supply only the credentials and permissions required for deployment.

This separation reduces the opportunities for installation-time code to access sensitive resources.

It is not a guarantee against every attack. A malicious package can still compromise the build or its output. However, minimizing available permissions can reduce the amount of additional access an attacker obtains.

For stronger protection, review third-party GitHub Actions and pin them to reviewed commit SHAs where practical. Keep actions updated as new security fixes become available.

Step 5: Secure npm publishing and trusted publishers

Package-publishing credentials are particularly valuable because they may allow an attacker to distribute malicious releases to downstream users.

Where available, consider npm Trusted Publishing with OpenID Connect rather than relying on long-lived publishing tokens.

Trusted Publishing can establish a relationship between the package registry and an authorized CI/CD identity. It should still be configured carefully.

GitHub announced on October 2, 2026, that unvalidated npm Trusted Publishing configurations expire after 48 hours. Following the first successful publish, a configuration becomes validated and is exempt from that expiration. GitHub also introduced restrictions on trusted-publishing tokens originating from certain GitHub Actions event types. (Official GitHub Changelog)

For developers maintaining npm packages, review the configuration rather than assuming it will remain valid indefinitely.

Recommended practices include:

Confirm the expected repository owner and repository name.

Publish from approved release workflows.

Protect the branches and environments that control publishing.

Grant the publishing job only the permissions it needs.

Avoid exposing publishing credentials to untrusted pull requests.

Review unexpected package releases and publishing activity.

Rotate any credential that may have been exposed.

Trusted Publishing reduces some risks associated with long-lived tokens, but it does not make a compromised release workflow harmless. An attacker who gains sufficient access to an authorized workflow may still attempt to publish malicious code through that workflow.

Step 6: Keep secrets away from untrusted build steps

A useful principle is to avoid giving a process access to secrets before it actually needs them.

For example, installing dependencies and running unit tests should not ordinarily require production database passwords or a production cloud administrator's credentials.

Keep secrets in the appropriate deployment environment rather than embedding them in source files or general-purpose build configurations.

For deployment workflows, use short-lived credentials where possible, with permissions scoped to the required resources.

Where your cloud provider supports workload identity federation or OIDC, evaluate it as an alternative to storing permanent cloud access keys in CI secrets.

Also review the lifetime and scope of GitHub and npm tokens. A credential with the ability to publish many packages or administer repositories increases the possible impact of a compromise.

Step 7: Investigate suspicious packages and installations

If a package is reported as compromised, do not simply uninstall it and assume the problem is resolved.

The malware may already have executed during installation.

Start by identifying the affected package and version, then determine which developer machines, build runners, and deployment jobs installed it.

For a project, you can begin by examining its dependency tree and lockfile:

npm ls --all

Search the lockfile for the affected package and compare the installed version with the applicable security advisory.

If the investigation indicates that malicious code executed, treat the relevant host or runner as potentially compromised.

From a separate, trusted environment:

Revoke exposed credentials and rotate affected secrets.

Investigate repository changes, unauthorized releases, and suspicious cloud activity.

Inspect build artifacts and shared dependency caches.

Rebuild affected systems from a known-good environment.

Restore application dependencies from a reviewed, trusted baseline.

Verify that downstream releases were not generated from a compromised build.

Pay particular attention to credentials that were accessible at installation time, including tokens stored in environment variables or configuration files.

If a compromised CI runner had access to a cloud account, investigate that account as well. Rebuilding the application alone does not invalidate credentials that may have been stolen.

Microsoft's ChainDrop investigation similarly recommends prioritizing credential rotation, unauthorized publishing, unexpected repository modifications, and artifacts produced by potentially affected build environments. (Microsoft Security Research)

A practical dependency-security checklist

For everyday development, I would focus on the following controls.

AreaRecommended practice
DependenciesReview changes to package.json and the lockfile
InstallationTreat lifecycle scripts as executable code
Vulnerability scanningRun npm audit and investigate relevant findings
Build pipelineUse npm ci with a reviewed lockfile
CI permissionsApply least-privilege GitHub Actions permissions
SecretsKeep production credentials out of general test jobs
Package publishingSecure trusted publishers and release workflows
MonitoringInvestigate unexpected package releases and repository changes
RecoveryRotate exposed credentials and rebuild potentially compromised systems

No single tool solves the entire problem. Lockfiles improve reproducibility, audit tools identify known vulnerabilities, and restricted CI permissions reduce the impact of malicious code. They work best together.

Conclusion

Modern applications depend on a large number of packages, and most development teams cannot manually review every line of every dependency.

That makes it especially important to secure the processes that install, test, and publish those dependencies.

The ChainDrop incident demonstrated how a compromised npm package can become an entry point into developer environments and build pipelines. GitHub's recent Trusted Publishing changes also show why package-release identities and workflow permissions deserve attention.

The practical lesson is not to stop using open-source packages. It is to be deliberate about what you install, which scripts execute, what permissions your build process receives, and which credentials are available at each stage.

A dependency should not automatically be trusted simply because it is already part of your project. Review changes, protect the build environment, restrict credentials, and investigate suspected compromises beyond the original package.