ghtier
← Back to Organization plane incidents
OrganizationSeptember 2025 / November 2025 / May 2026

Shai-Hulud self-propagating worm (1.0 & 2.0)

The worm made GitHub permissions the propagation mechanism. It validated available GitHub tokens and their permissions, then queried /user/repos?affiliation=owner,collaborator,organization_member to find repos where the compromised account had enough privilege. It planted shai-hulud-workflow.yml, exfiltrated ${{ toJSON(secrets) }} to a webhook, and made repos public, including any the victim merely held tokens for. The 2.0 variant registered the infected host as a self-hosted Actions runner named SHA1HULUD and planted an injection-prone workflow at .github/workflows/discussion.yaml, so opening a GitHub Discussion gave arbitrary command execution on the host. 2.0 compromised 796 npm packages, with credential exfiltration confirmed across more than 500 GitHub users spanning 150+ organizations. A smaller descendant, known as Mini Shai-Hulud, hit in May 2026: on 19 May one developer account takeover led to more than 630 malicious versions across 317 npm packages in about 20 minutes, including AntV packages. The TanStack compromise a week earlier came through a different door, Actions cache poisoning (see the cache poisoning incident).

Permissions abused

Enumerated token permissions via /user/reposcontents:write (public flip, workflow injection)Self-hosted runner registration (2.0)

How it unfolded

  1. 1

    Available GitHub tokens are validated and scoped

    After the worm reached a host — typically via a compromised npm package — its payload validated whatever GitHub tokens were reachable there and checked exactly what each one could do.

  2. 2

    Repos are enumerated by privilege, not just ownership

    It queried /user/repos?affiliation=owner,collaborator,organization_member to find every repository where the compromised account held enough privilege to act — far beyond repos it directly owned.

  3. 3

    A malicious workflow is planted and secrets exfiltrated

    It planted shai-hulud-workflow.yml in each reachable repo and exfiltrated the full secrets context (${{ toJSON(secrets) }}) to an external webhook.

  4. 4

    Repositories are flipped public to maximize spread

    It made repositories public, including ones the compromised account merely held a token for rather than owned outright, maximizing both exposure and further propagation surface.

  5. 5

    2.0: the infected host registers itself as a runner

    The follow-up variant registered the infected machine as a self-hosted Actions runner named SHA1HULUD, turning a single compromised developer machine into infrastructure the attacker could keep using.

  6. 6

    2.0: opening a GitHub Discussion becomes remote code execution

    It planted an injection-prone workflow at .github/workflows/discussion.yaml, so simply opening a GitHub Discussion on an affected repo triggered arbitrary command execution on that self-hosted runner — ultimately compromising 796 npm packages, with credential exfiltration confirmed across 500+ GitHub users spanning 150+ organizations.

Mitigations

  • Disallow self-hosted runners on public repositories — the 2.0 variant's persistence relied entirely on registering one

  • Prevent branch and workflow-run deletion — a propagating worm's cleanup steps can't erase the evidence trail

  • Set the default GITHUB_TOKEN permission to read-only at the org level — limits what any single compromised token can enumerate and write

  • Enable secret scanning and push protection — catches exfiltration attempts in an injected workflow

  • Audit and revoke stale PATs/SSH keys on a recurring basis — compromise of one maintainer's npm-side token was enough to start the chain

Frameworks

Part of

Contained. First seen September 2025, last seen May 2026. May 2026 wave: more than 630 malicious versions across 317 npm packages in about 20 minutes (as of 19 May 2026, source). Mapping by ghtier, not yet reviewed. Threat landscape