Blog / Threat Analysis

Anatomy of a Worm: The 2025 npm Supply Chain Attacks Through the TLCTC Lens

Why the industry’s favorite label — “supply chain attack” — hides more than it reveals, and how causal decomposition shows what actually happened.

BK
Bernhard Kreinz
Loading read time...

The Semantic Problem

Between August and November 2025, three attack waves tore through the npm ecosystem. Headlines called them “supply chain attacks.” Security vendors published reports on “the npm supply chain compromise.” CISOs asked whether their organizations were “exposed to supply chain risk.”

None of those statements is exactly wrong. All of them are operationally weak.

Calling these incidents “supply chain attacks” is like calling a multi-car pileup “a road event.” It gestures at the setting, not the cause. It tells you where the incident happened, not which generic vulnerabilities were exploited, in what sequence, at what speed, and where the chain could have been broken.

That is precisely the problem TLCTC is built to solve.

By decomposing a cyber incident into its constituent causal steps — each mapped to exactly one of ten mutually exclusive threat clusters — TLCTC replaces vague category labels with a concrete attack path. That path does not merely describe the incident. It explains it. It tells you what was exploited, what enabled the next step, and which control families could have interrupted the chain.

This post applies that decomposition to the three major 2025 npm campaigns in full depth. The attack paths are not summaries. They are the analysis.

Why npm Is Such a Strong Case for Causal Analysis

Before breaking down the campaigns, it is worth understanding why npm is such a revealing case for TLCTC.

npm operationalizes trust at industrial scale. A developer or CI pipeline does not manually inspect every transitive dependency. It delegates trust to package names, version ranges, lockfile state, registry metadata, maintainer accounts, provenance signals, integrity hashes, and the package manager’s own resolution logic.

In TLCTC terms, the ecosystem is full of trust artifacts — tarballs, manifests, resolved versions, signatures, semver ranges — and trust decisions — the moments when a developer workstation, build runner, or CI pipeline accepts one of those artifacts and integrates it into the organization’s software reality.

That moment of integration is the Trust Acceptance Event. It is the point at which a third-party component is accepted as authoritative and treated as part of the local environment. This is where #10 Supply Chain Attack occurs.

But #10 is only the boundary-crossing step. It is not a bucket that swallows everything before and after it.

That distinction drives the entire rest of the analysis. Once a package has crossed the trust boundary, the next question is not “is this still supply chain?” The next question is: what generic vulnerability is being exploited now?

The Canonical npm Attack Path: #10 → #1 → #7

In npm ecosystems, the answer almost always includes two steps that the industry routinely collapses into one.

First, the package manager, build runner, or install workflow uses legitimate functionality to fetch, resolve, unpack, and process the accepted dependency. That is #1 Abuse of Functions. The package manager is not broken. It is doing exactly what it was designed to do.

Second, attacker-controlled code actually executes through a lifecycle hook, install script, or an interpreter’s designed execution capability. That is #7 Malware. Foreign code runs through an intended execution mechanism.

That yields the canonical npm chain:

#10 → #1 → #7
Click to Enlarge
#10 Supply Chain Trust Acceptance ||[@npm→@Org]|| #1 Abuse of Func Install Processing #7 Malware Payload Execution
Figure 1: The Canonical npm Attack Path. The package manager's processing behavior is an explicit, controllable step.

This is deeper and more useful than simply writing #10 → #7, because the middle step is where defenders most often deceive themselves. The package manager’s processing behavior is not invisible plumbing. It is a distinct causal step with its own generic vulnerability — insufficient restriction on the scope of legitimate functionality — and its own control surface.

Controls at this step include namespace restrictions, registry allowlists, lifecycle-script policy, resolver constraints, and install sandboxing. Compressing #10 → #1 → #7 into #10 → #7 hides that entire control surface.

One more clarification matters up front: npm incidents are usually not #2 or #3 stories. In the major 2025 campaigns, the attackers did not need a server-side or client-side implementation flaw in the package manager or registry. They published malicious packages, hijacked maintainer identities, abused lifecycle behavior, and won dependency resolution through expected functionality. The dominant clusters are #10, #1, #4, #7, and #9.

That distinction matters because the control implications are different.

  • #2 / #3 call for secure coding and patching.
  • #10 calls for trust governance.
  • #1 calls for misuse-resistant workflow design.
  • #7 calls for execution control.
  • #4 calls for credential protection and abuse resistance.

TLCTC Notation Reference

For readers new to the notation, these are the operators used in the paths below.

Operator Meaning
#X → #Y Cluster X enables or leads to Cluster Y
→[Δt=value]→ Temporal gap between steps
||[context][@Source→@Target]|| Domain boundary crossing
||[context][@Source⇒@Transit→@Target]|| Transit boundary with intermediary infrastructure
+ [DRE: C/I/Ac/Av] Data Risk Event annotation
#10.1 / #10.2 Supply chain subtype: Update Vector / Development Vector

The ten clusters, in shorthand, are: #1 Abuse of Functions, #2 Exploiting Server, #3 Exploiting Client, #4 Identity Theft, #5 Man in the Middle, #6 Flooding Attack, #7 Malware, #8 Physical Attack, #9 Social Engineering, #10 Supply Chain Attack.

The Three Campaigns

  • Campaign 1: S1ngularity / Nx (August 26, 2025) — Attackers abused a vulnerable GitHub Actions workflow in the Nx monorepo tool repository to steal a publishing token, pushed trojanized packages to npm, and deployed QUIETVAULT to harvest environment secrets. In a notable twist, the payload weaponized an LLM coding assistant already present on victim machines to search for credentials.
  • Campaign 2: Chalk / Debug Phishing (September 8, 2025) — Attackers phished the maintainer of 18 foundational npm packages, including chalk and debug, then published malicious versions containing obfuscated browser-side code that hijacked cryptocurrency transactions.
  • Campaign 3: Shai-Hulud (September 14 – November 2025) — A self-replicating supply chain worm that harvested npm, GitHub, AWS, and GCP credentials; republished every package the victim maintained with the worm injected; and created malicious GitHub Actions workflows for persistence and exfiltration. A later wave added preinstall execution and a destructive wipe fallback.

Campaign 1: S1ngularity / Nx

What Actually Happened

The Nx repository used a GitHub Actions pull_request_target workflow. By design, that trigger runs in the context of the base repository, meaning it can access repository secrets. The workflow was configured to check out and run code from the incoming pull request inside that privileged context.

The attacker submitted a pull request. CI executed attacker-controlled code with elevated privileges. The code extracted the GITHUB_TOKEN and an npm publishing token. The attacker then used the stolen npm token to publish trojanized versions of nx and related packages. Those versions contained a postinstall script that launched QUIETVAULT, a credential stealer that harvested environment variables, GitHub PATs, cloud tokens, and — unusually — invoked an LLM tool already installed on the developer’s machine to search for additional secrets. Exfiltrated data was committed to a public GitHub repository.

TLCTC Decomposition

Phase A — Compromising the Nx build pipeline

#1 →[0s] #7 + [DRE: C]
  • Step 1 — #1 Abuse of Functions: The attacker submits a pull request, which is a legitimate GitHub function. The pull_request_target workflow is also behaving as designed: it runs with base-repository privileges. The failure is not primarily an implementation bug. It is that the workflow’s scope of legitimate functionality was too broad. Untrusted pull request content was allowed into a privileged execution path.
    This is textbook #1. A legitimate function is operating with unsafe scope.
  • Step 2 — #7 Malware: GitHub Actions executes the attacker’s code. That is foreign code running through an intended execution mechanism. The payload steals the GITHUB_TOKEN and npm publish token.
    + [DRE: C] marks the confidentiality loss associated with credential theft. Per the R-CRED rule, this is credential acquisition, not credential use.

Phase B — Publishing the trojanized package

#4 →[minutes] #1 + [DRE: I]
  • Step 3 — #4 Identity Theft: The attacker uses the stolen npm token to authenticate as the Nx maintainer. The moment stolen credentials are used, the cluster is #4.
  • Step 4 — #1 Abuse of Functions: Using that authenticated session, the attacker invokes npm’s legitimate publish function to push trojanized package versions. npm is not malfunctioning. The publish workflow is being abused.
    + [DRE: I] marks the integrity compromise of the package content.

Phase C — Trust acceptance and consumer-side compromise

||[dev][@Nrwl(Nx)⇒@npm→@Consumers]|| #10.2
→[hours-days] #1 →[0s] #7
→[0s] #1 + [DRE: C] →[0s] #7 →[0s] #1 + [DRE: C]
  • Domain boundary: ||[dev][@Nrwl(Nx)⇒@npm→@Consumers]|| marks the trust transition. The malicious package originates from the compromised maintainer identity, transits the npm registry, and is accepted into consumer environments.
  • Step 5 — #10.2 Supply Chain Attack (Development Vector): This is the Trust Acceptance Event. Consumers pull the compromised package during dependency installation in development or build workflows. The third-party dependency relationship is the enabling condition.
  • Step 6 — #1 Abuse of Functions: The consumer package manager resolves the dependency, downloads the tarball, unpacks it, processes package.json, and prepares the lifecycle hook environment. All of this is legitimate functionality behaving exactly as designed.
    This step matters analytically because it is distinct from the execution that follows. The tool is not yet running foreign code. It is processing the artifact the trust decision allowed.
  • Step 7 — #7 Malware: The postinstall payload executes. That is foreign code running through npm’s intended lifecycle mechanism.
  • Step 8 — #1 Abuse of Functions: QUIETVAULT uses filesystem APIs, environment access, and operating system functions to harvest credentials and system information. At this point, no new foreign code is introduced. The already-running malware is abusing legitimate functions.
    + [DRE: C] marks the resulting confidentiality loss.
  • Step 9 — #7 Malware: QUIETVAULT downloads and launches TruffleHog, or weaponizes an LLM coding assistant already present on the endpoint. Under R-EXEC, each new foreign-code execution step is a distinct #7.
  • Step 10 — #1 Abuse of Functions: The malware uses legitimate GitHub APIs to create a public repository and commit stolen data. GitHub’s functions are working exactly as designed.
    + [DRE: C] marks exfiltration.

Consolidated Attack Path

#1 →[0s] #7 + [DRE: C]
→[minutes] #4 →[0s] #1 + [DRE: I]
||[dev][@Nrwl(Nx)⇒@npm→@Consumers]|| #10.2
→[hours-days] #1 →[0s] #7
→[0s] #1 + [DRE: C] →[0s] #7 →[0s] #1 + [DRE: C]

The velocity profile is revealing. The upstream compromise and publication happen within minutes. Propagation to consumers depends on install timing and unfolds over hours or days. Once a consumer triggers installation, the local #1 → #7 sequence fires almost immediately.


Campaign 2: Chalk / Debug Phishing

What Actually Happened

Attackers registered the lookalike domain npmjs.help, impersonated npm support, and sent phishing emails to Josh Junon, maintainer of chalk, debug, ansi-styles, strip-ansi, and other widely used packages. Under urgency and time pressure, Junon entered credentials into the phishing site. The attackers then authenticated to npm as the maintainer and published malicious versions of 18 or more packages.

The payload was obfuscated JavaScript designed for browser environments. It hooked window.ethereum, fetch, and XMLHttpRequest, then replaced cryptocurrency wallet addresses with attacker-controlled ones using similarity logic and a broad wallet set for redundancy.

TLCTC Decomposition

Phase A — Credential acquisition through phishing

#9 →[Δt≈3d] + [DRE: C]
  • Step 1 — #9 Social Engineering: The phishing email exploits human psychology: authority bias, urgency, and fatigue. No implementation flaw is required. The human is manipulated into providing credentials.
    + [DRE: C] marks credential exposure.

The Δt≈3d annotation is worth keeping because it surfaces a real defensive window: the phishing infrastructure existed for days before success.

Phase B — Account takeover and package poisoning

#4 →[0s] #1 + [DRE: I]
  • Step 2 — #4 Identity Theft: The attacker authenticates to npm using the stolen credentials. This is direct impersonation.
  • Step 3 — #1 Abuse of Functions: The authenticated session is used to publish malicious package versions through npm’s legitimate API.
    + [DRE: I] marks the integrity compromise of the package artifacts.

Phase C — Trust acceptance and downstream execution

||[update][@Maintainer(Qix)⇒@npm→@Consumers]|| #10.1
→[hours] #1 →[0s] #7 →[0s] #1 + [DRE: C, I]
  • Domain boundary: ||[update][@Maintainer(Qix)⇒@npm→@Consumers]|| marks the transition from the compromised upstream maintainer identity, via npm, into consumer environments.
  • Step 4 — #10.1 Supply Chain Attack (Update Vector): This is the Trust Acceptance Event. Existing trusted packages receive poisoned new versions, and consumers pull those versions through the normal update channel. That makes this an update vector, not a development vector.
  • Step 5 — #1 Abuse of Functions: The consumer package manager resolves the updated version, downloads the tarball, and processes it through normal install behavior. Again, this is legitimate functionality applying the earlier trust decision.
  • Step 6 — #7 Malware: The malicious JavaScript executes in the browser through the browser’s intended execution capability.
  • Step 7 — #1 Abuse of Functions: The payload hooks legitimate browser APIs and uses them for purposes outside their intended scope: intercepting and modifying cryptocurrency transactions.
    + [DRE: C, I] captures both confidentiality and integrity harm: data is intercepted, and wallet destinations are altered.

Consolidated Attack Path

#9 →[Δt≈3d] + [DRE: C]
→[0s] #4 →[0s] #1 + [DRE: I]
||[update][@Maintainer(Qix)⇒@npm→@Consumers]|| #10.1
→[hours] #1 →[0s] #7 →[0s] #1 + [DRE: C, I]

This campaign makes one point especially clear: “supply chain attack” describes the trust boundary crossing, but it does not explain the credential theft, the maintainer impersonation, or the browser-side abuse that followed.

Variant: Typosquatting

The three headline 2025 campaigns did not depend on typosquatting, but npm has a long history of lookalike package attacks, and the pattern is important because it introduces a different front-end cause: #9 before #10.

A developer under time pressure installs expresss, loadsh, or another lookalike instead of the intended package.

#9 →[Δt=variable] #10.2 ||[dev][@Public⇒@npm→@Org]|| → #1 → #7
  • Step 1 — #9 Social Engineering: The generic vulnerability is human susceptibility to visual similarity, haste, and naming confusion. The registry is not malfunctioning. The human is being manipulated into authorizing the wrong trust decision.
  • Step 2 — #10.2 Supply Chain Attack: The wrong third-party package is accepted into the environment.
  • Steps 3–4 — #1 → #7: The standard install-processing and execution chain follows.

This distinction matters because the controls differ. Human verification habits and review friction address the #9 step. Provenance policy, trust governance, and dependency restrictions address #10.


Campaign 3: Shai-Hulud — The Recursive Supply Chain Worm

What Actually Happened

Shai-Hulud was structurally different from the other campaigns because it was recursive.

The likely patient zero was rxnt-authentication version 0.0.3, published on September 14, 2025. The initial maintainer compromise is assessed as likely related to credentials leaked during the earlier S1ngularity campaign, though some reporting suggested phishing.

Once the compromised package was installed, the payload executed during npm’s postinstall phase and began a cascading sequence:

  • It harvested npm, GitHub, AWS, and GCP credentials from the local environment.
  • It downloaded and executed TruffleHog for deeper secret scanning.
  • If GitHub credentials were found, it created public repositories for exfiltration, injected malicious GitHub Actions workflows, created branches and pull requests, and attempted to expose private repositories.
  • If npm credentials were found, it enumerated every package maintained by the victim, injected the worm into each package’s postinstall path, and published new versions.

That final step is what makes the campaign a worm. Every newly published package became a new infection source for downstream consumers, who could themselves become new propagation nodes.

A later wave, commonly called Shai-Hulud 2.0, moved execution earlier into preinstall and added a destructive fallback: if exfiltration failed, it attempted to wipe the victim’s home directory.

TLCTC Decomposition

The key structural point is that the worm creates a recursive loop of supply chain trust crossings. Each newly compromised maintainer identity generates fresh #10 transitions downstream.

Phase A — Initial compromise of patient zero

(#9 | #4) →[0s] #1 + [DRE: I]
  • Step 1 — #9 Social Engineering or #4 Identity Theft: The exact initial compromise remains somewhat ambiguous. If leaked credentials from S1ngularity were reused, the key step is #4. If a new phishing event occurred, the initial cause is #9. That ambiguity is itself analytically useful, because it shows how one campaign’s credential exposure can seed another campaign’s entry path.
  • Step 2 — #1 Abuse of Functions: The compromised account is used to publish a trojanized package.
    + [DRE: I] marks the integrity loss of the package artifact.

Phase B — First trust acceptance: patient zero to Victim₁

||[update][@Patient0⇒@npm→@Victim₁]|| #10.1
→[hours] #1 →[0s] #7
  • Step 3 — #10.1 Supply Chain Attack (Update Vector): Victim₁ accepts the compromised package through the npm trust channel.
  • Step 4 — #1 Abuse of Functions: The victim’s package manager resolves, downloads, unpacks, and processes the package.
  • Step 5 — #7 Malware: The postinstall payload runs. This is the first #7 event on Victim₁’s machine.

Phase C — Local exploitation on Victim₁

→[0s] #1 + [DRE: C]
→[0s] #7 →[0s] #1 + [DRE: C]
→[0s] #4 →[0s] #1 + [DRE: C, I]
  • Step 6 — #1 Abuse of Functions: The worm sweeps the filesystem for credential stores, configuration files, and cloud keys using legitimate file and OS functions.
    + [DRE: C] marks the resulting confidentiality loss.
  • Step 7 — #7 Malware: The worm downloads and executes TruffleHog. Under R-EXEC, this is a distinct #7 because new foreign code is introduced and run.
  • Step 8 — #1 Abuse of Functions: TruffleHog scans the filesystem using legitimate read and pattern-matching capabilities.
    + [DRE: C] applies again.
  • Step 9 — #4 Identity Theft: The worm uses stolen GitHub credentials to authenticate as the victim.
  • Step 10 — #1 Abuse of Functions: It then uses legitimate GitHub APIs to create exfiltration repositories, upload workflows, create branches, open pull requests, and expose data.
    + [DRE: C, I] captures both data exposure and repository integrity compromise.

Phase D — The worm propagation loop

#4 →[0s] #1 + [DRE: I]
||[update][@Victim₁⇒@npm→@Victim₂...ₙ]|| #10.1
→ [RECURSIVE: repeat Phase B–D for each new victim]
  • Step 11 — #4 Identity Theft: The worm uses stolen npm credentials to authenticate as Victim₁.
  • Step 12 — #1 Abuse of Functions: It injects the payload into every maintained package and publishes new versions.
    + [DRE: I] marks the integrity compromise of each package.
  • New trust boundary: ||[update][@Victim₁⇒@npm→@Victim₂...ₙ]|| #10.1 is the crucial recursive step. Each newly poisoned package creates a fresh trust acceptance event for downstream consumers. Every new victim replays the canonical npm chain: #10.1 → #1 → #7
    That is why this campaign is better understood as a supply chain fractal than as a single compromise.

Phase E — Destructive fallback in Shai-Hulud 2.0

#1 + [DRE: Ac]

If the worm fails to exfiltrate or propagate, it attempts to delete the victim’s home directory using standard filesystem deletion functions. That is #1, not #7, because the destructive act is performed through legitimate operating system functionality already under the worm’s control.

+ [DRE: Ac] is the right annotation because the core harm is loss of access to data, not system-wide loss of availability.

Consolidated Attack Path (Single Recursion Depth)

(#9 | #4) →[0s] #1 + [DRE: I]
||[update][@Patient0⇒@npm→@Victim₁]|| #10.1
→[hours] #1 →[0s] #7
→[0s] #1 + [DRE: C] →[0s] #7 →[0s] #1 + [DRE: C]
→[0s] #4 →[0s] #1 + [DRE: C, I]
→[0s] #4 →[0s] #1 + [DRE: I]
||[update][@Victim₁⇒@npm→@Victim₂...ₙ]|| #10.1
→ [RECURSIVE]

The Notation Challenge: Recursion

Shai-Hulud exposes a genuine edge case in attack-path notation. Most attack paths are acyclic. This one is not.

The [RECURSIVE] annotation is therefore not decorative. It marks a structural return. Each propagation hop generates a new #10.1 trust boundary crossing, and each hop repeats the full #10.1 → #1 → #7 consumer-side chain.

Its velocity profile is also unusual. Local exploitation on each infected node occurs in seconds, but the next propagation hop depends on when the next consumer runs npm install or otherwise accepts the poisoned update. The result is a pulsed propagation model: machine-speed execution on each node, human- and workflow-speed delay between nodes.

What the Decomposition Reveals

  1. “Supply Chain Attack” Names a Boundary, Not a Mechanism
    In all three campaigns, #10 appears at the trust boundary. It marks the moment one organization accepts a third-party artifact as authoritative. It does not explain the credential theft, maintainer impersonation, workflow abuse, or payload execution that made the crossing possible and consequential.
  2. The #1 Between #10 and #7 Is Where Defenders Most Often Lose Resolution
    The package manager’s install-processing behavior is a distinct causal step. That means it has its own controls. If you collapse #10 → #1 → #7 into #10 → #7, you erase one of the most important defensive surfaces in the npm ecosystem.
  3. The R-CRED Rule Exposes the Kill Chain Cleanly
    These campaigns repeatedly follow the same pattern: credential acquisition through one cluster, then credential use through #4. That makes #4 a structurally decisive break point. Controls such as phishing-resistant MFA, scoped tokens, short-lived credentials, and publish approval workflows do not merely reduce risk in the abstract. They sever the chain between acquisition and exploitation.
  4. #1 Abuse of Functions Is the Silent Majority
    Count the #1 steps in the three campaigns. They dominate the path count. npm publishing, dependency resolution, install processing, GitHub API use, filesystem access, browser API hooking, and destructive file deletion all rely on legitimate functions being used beyond their intended scope. Modern attacks often win not by breaking software, but by letting software do exactly what it was designed to do in the wrong trust context.
  5. R-EXEC Reveals the Multi-Stage Nature of “Malware”
    Campaign 1 and Campaign 3 each contain more than one #7 step. The initial payload runs first; later, new tools or orchestration components are downloaded and executed. Those are not analytic duplicates. Each distinct #7 is a separate control opportunity.
  6. Shai-Hulud Turns Supply Chain into a Fractal
    The worm is not just “a supply chain attack that spreads.” It is a recursive structure in which each new victim becomes a source of fresh #10.1 crossings. That makes the campaign qualitatively different from a one-way vendor-to-customer trust compromise.
  7. DRE Precision Matters Operationally
    Shai-Hulud 2.0’s wipe behavior is + [DRE: Ac], not + [DRE: Av]. The system can continue running while the data becomes inaccessible. That distinction drives different recovery logic.

Controls Mapped to the Causal Chain

The bow-tie model only becomes useful when controls are mapped to specific causal steps rather than to vague incident categories.

Cluster Step Generic Vulnerability Preventive Control
#9 (Phishing) Human psychological susceptibility Phishing-resistant MFA, typosquat domain monitoring, training focused on urgency and authority bias
#1 (CI/CD workflow abuse) Excessive scope of legitimate functionality Least-privilege GitHub Actions workflows, never executing untrusted PR code in privileged contexts, restricted workflow permissions
#4 (Token use) Insufficient protection of identity credentials Hardware-backed MFA, scoped publish tokens, publish approval workflows, time-limited credentials
#10 (Trust acceptance) Third-party trust dependency SBOM governance, provenance checking, staged dependency rollout, internal mirrors, lockfile-enforced review
#1 (Install processing) Excessive scope of legitimate functionality Namespace restrictions, registry allowlists, sandboxed install environments, lifecycle-script controls, resolver restrictions
#7 (Payload execution) Intended execution capability Application whitelisting, postinstall audit policy, build sandboxing, code-signing enforcement
#4 (Post-compromise token use) Insufficient protection of identity credentials Token rotation, short-lived credentials, separate publish and build credentials, MFA re-verification
#1 (Exfiltration through legitimate APIs) Excessive scope of legitimate functionality Egress filtering, GitHub and cloud API restrictions from build environments, network segmentation

Notice that #1 appears multiple times for different operational contexts. That is not redundancy. It is the framework exposing multiple distinct abuse surfaces that a flatter taxonomy would hide.

On the detection side, the most useful signals are behavioral: anomalous publish velocity, mass branch creation, workflow injection, private-to-public repository changes, lockfile drift, or unexpected network egress from build systems.

On the consequence side, response priorities include credential revocation, dependency rollback, SBOM-driven blast-radius assessment, and post-incident analysis that documents the actual causal path rather than retreating to “we suffered a supply chain attack.”

The Package Is Not the Attack. The Accepted Trust Decision Is.

This is the deepest lesson of the 2025 npm incidents.

The most dangerous thing about an npm supply chain attack is not that malicious code exists somewhere in the ecosystem. Malicious code has always existed. The dangerous thing is that your environment decides to treat that code as if it belongs there.

The Trust Acceptance Event is where organizational sovereignty over the software stack is ceded. Everything that follows is your own tooling faithfully executing a trust decision that should either never have been made or should have been constrained far more tightly.

That is why the deepest meaningful notation for an npm incident is not just #10. It is the sequence that follows trust acceptance:

#10 → #1 → #7

Trust was accepted. Legitimate functions processed the result. Foreign code executed. And then, often, the path continued into #4 → #1: credentials were used, and legitimate APIs carried the attacker’s objectives the rest of the way.

The 2025 npm campaigns were not “supply chain attacks” in any analytically sufficient sense. They were multi-cluster campaigns in which social engineering (#9), function abuse (#1), identity theft (#4), malware execution (#7), and supply chain exploitation (#10) each played distinct causal roles at distinct points in the chain.

Calling the whole thing “a supply chain attack” is like diagnosing a patient with “hospital.” The patient was in a hospital. The diagnosis is elsewhere.

TLCTC does not add complexity for its own sake. It adds causal resolution. And causal resolution is the difference between buying a “supply chain security tool” and actually hardening CI/CD workflows, enforcing phishing-resistant MFA, sandboxing install behavior, constraining execution, governing trust acceptance, and monitoring for credential abuse — because you can name, at each step, what failed and what would have broken the chain.

Quick Reference: npm Attack Path Patterns
  • Canonical npm install-time compromise
    #10.2 ||[dev][@Public⇒@npm→@Org]|| → #1 → #7
  • Maintainer-account compromise with phishing
    #9 + [DRE: C] → #4 → #1 + [DRE: I] ||[update][@Vendor⇒@npm→@Org]|| #10.1 → #1 → #7
  • Typosquatting
    #9 → #10.2 ||[dev][@Public⇒@npm→@Org]|| → #1 → #7
  • Build-to-secret-theft chain
    #10.2 ||[dev][@Public⇒@npm→@Org]|| → #1 → #7 → #1 + [DRE: C] → #4 → #1 + [DRE: C, I]
  • Self-replicating worm pattern
    #10.1 → #1 → #7 → #1 + [DRE: C] → #7 → #1 + [DRE: C] → #4 → #1 + [DRE: I] → #10.1 → [RECURSIVE]

Framework version: TLCTC v2.0 with v2.1 draft transit notation | Date: March 2026