Graph the Planet: Shai-Hulud 2.0
TL;DR: This blog looks at the Shai-Hulud 2.0 worm through an Attack Path Management lens. I also introduce NPMHound which can be used to visualize NPM package dependencies in BloodHound OpenGraph.
Introduction
A recent supply chain attack, referred to as Shai-Hulud 2.0, involved an attacker using a worm to infect developer systems that contained credentials for various technology platforms. The underlying secrets were exfiltrated to a network of public GitHub repositories. Presumably, the attacker (or new attackers) retrieved these secrets. At a high level, this is depicted in the following graph.

Decomposing Edges
I wanted to better understand the initial infection. That is, I wanted to decompose the “StartsInitialInfection” edge.

PostHog was one organization this campaign targeted. They have provided a detailed postmortem here. In their case, the infection started with a PWN request. This was noted in the analysis performed by Wiz in a blog here. Attackers can use PWN requests to have their malicious code run using GitHub workflows. The malicious code can be crafted to expose credentials available in the runner environment. A screenshot of a commit that abused this configuration is provided below. This led to the environment variables being sent to a webhook that the attacker controlled.

The environment variables included a GitHub personal access token (PAT) for one of PostHog’s bots. This bot identity had broad repository write permissions across their GitHub organization per the post mortem. Let’s pause to graph some of what has been discussed so far.

PostHog indicated that the threat actor leveraged the GitHub PAT to gain access to additional credentials including an NPM token. We can add this information to our graph.

The attacker leveraged the exposed NPM token to infect writable NPM packages. These infected packages were propagated to other systems as the Shai-Hulud 2.0 worm. This gives us a high-level decomposition of the “StartsInitialInfection” edge in the graph below.

Worm Propagation Cycle
With the initial infection understood a bit better, we can shift focus to how the worm propagated itself. The worm gets installed on some sort of developer system. This system could be a developer workstation, CI runner, etc. After the malware executes, it would harvest credentials on the system. If an NPM token was found, the malware would attempt to infect any modifiable NPM packages the token has access to. These packages are then published to NPM and this propagates the worm to new victim systems through software supply chain dependencies. We can summarize this with the following graph cycle.

Through this graph cycle, the worm propagated to other NPM packages.
Semantic Versioning
The semantic versioning that NPM packages use was interesting to me. I wanted to understand the version range syntax as it helps explain how the infection spread. The Shai-Hulud malware would bump the package versions (x.y.z → x.y.z+1). If a dependent package used the caret (^x.y.z), tilde (~x.y.z), or greater than range (>x.y.z), then those packages will accept the new version at the next update. This would have downstream effects based on range syntax of:
- The dependencies of the dependent package
- The dependencies of the dependencies of the dependent package (and so on)
Modeling these dependencies was something I wanted to better understand. If I were performing incident response for this type of malware, I would be hesitant to install any NPM packages. I worked on a project called NPMHound to model these dependency chains in BloodHound OpenGraph. It leverages the registry.npmjs.org API so no NPM packages get installed. A quick shoutout and thank you to Andy Robbins for helping me with this project. Below is a random sample of some dependency trees of different lengths collected using NPMHound.

We can restrict our view to the edges that might be susceptible to attacks like Shai-Hulud by changing our cypher query to only return paths where the edges have the constraint_type equal to the ‘caret’ property. Updates to upstream packages propagate down the dependency tree structures to dependent packages.

Credential Exfiltration
The worm captured other credentials as well. The credentials were exfiltrated to a network of public repositories on GitHub. This exfiltration was done by either creating a new repository using newly compromised GitHub tokens or by using previously compromised GitHub tokens. In the second case, previously compromised GitHub tokens were retrieved from the exfiltration network. In this way, a victim of this malware may have had their credentials exfiltrated to a repository they do not control. Each repository had a description of “Sha1-Hulud: The Second Coming.” On November 25, 2025, I searched GitHub for this string and took a screenshot to get a sense of the scale.

We can summarize this in the following graph. The “Exfil Repository Network” node on the right represents the 13.7k repositories in the previous screenshot.

Impact
Let’s combine what we have described thus far.

This graph shows an overview of the attack. The attacker targeted at least one repository susceptible to PWN request attacks. The infected packages ran on developer systems such as workstations or CI runners. The malware used various credential harvesting techniques to find credentials. This is depicted as “ContainsCredentialsFor” edges originating from the developer computer node to various types of secrets. I discussed this edge in a previous blog post. The credentials are then exfiltrated to a network of public repositories. At this point, anyone could access the exfiltrated credentials simply by viewing the contents of these public repositories.
Defensive Strategies
With this mental model in place, how can security teams approach defending against an attack like this? Our strategy is to leverage attack path management (APM). A key characteristic of APM is modeling attack paths in an organization’s graph and finding the choke points. As defenders, we want to break the attack paths and cycles in the attack graph. An organization should have some control over the “ShaiHuludInfectsModifiablePackages”, “ContainsCredentialsFor”, and “InstalledOn” edges. How can we mitigate risk or remove these edges?
The “ShaiHuludInfectsModifiablePackages” edge exists because long lived NPM tokens were exposed on the compromised developer computers. The security best practices from NPM say to “prefer trusted publishing over tokens”. Therefore, to remove this edge, we should follow the security best practices that NPM provides.
For the “ContainsCredentialsFor” edge, the attacker ran TruffleHog and performed other credential harvesting techniques. They eliminated any uncertainty around whether the developer machines contained credential material. As defenders, we can also use secret scanning tools like TruffleHog to scan developer systems and eliminate uncertainty. Another OpenGraph extension I created called SecretHound can model TruffleHog output in BloodHound. The intent here is to allow security teams to keep an inventory of where credentials are in their organization’s graph. This facilitates preparation for potential future incident response activities such as revoking or rotating exposed credentials.
Lastly, for the “InstalledOn” edge, one can use the tool I mentioned earlier: NPMHound. The goal here is to inventory the nested supply chain dependencies of NPM packages in your software and consider what semantic versioning ranges best fit your threat model. The tool is meant to enable both incident response for supply chain attacks like this and modeling the supply chain dependencies of applications I run into on offensive security assessments. Fortunately, the registry.npmjs.org API provides a lot of useful information. I used this API for data collection for NPMHound.
References:
https://about.gitlab.com/blog/gitlab-discovers-widespread-npm-supply-chain-attack
https://thehackernews.com/2025/11/second-sha1-hulud-wave-affects-25000.html
https://www.sysdig.com/blog/return-of-the-shai-hulud-worm-affects-over-25-000-github-repositories
https://posthog.com/blog/nov-24-shai-hulud-attack-post-mortem
https://www.wiz.io/blog/shai-hulud-2-0-ongoing-supply-chain-attack
https://www.wiz.io/blog/shai-hulud-2-0-aftermath-ongoing-supply-chain-attack
https://securitylab.github.com/resources/github-actions-preventing-pwn-requests
https://www.aikido.dev/blog/shai-hulud-strikes-again-hitting-zapier-ensdomains
https://www.sysdig.com/blog/return-of-the-shai-hulud-worm-affects-over-25-000-github-repositories
https://corridor.dev/shai-check