// threat brief

No CVE ID

PostHog's official npm packages hit by the Shai-Hulud 2.0 worm: a misconfigured GitHub workflow handed attackers the publish token

CVSS
—
CISA KEV
Not listed
Weakness
CWE-506

PlainCVE Team · Published 2026-09-30 · AI-assisted · Press brief ↓

AI review 2026-09-30 (not human-reviewed)Command tests: none yetReproduced: not yetVerification record ↓

Attackers stole PostHog's npm publish token through a misconfigured GitHub workflow and shipped worm-infected SDKs that steal credentials on install.

Who is affectedDeveloper machines or CI jobs that installed posthog-node 4.18.1/5.11.3/5.13.3, posthog-js 1.297.3 or another malicious PostHog version (OSV lists at least 56 packages) from 04:04 UTC on 2025-11-24 until the versions were deleted (PostHog says 09:30 UTC; some packages show later last-modified times on npm), with install scripts allowed to run. PlainCVE's judgement: machines that later reinstalled one of those versions from a lockfile, local cache or internal mirror should also be treated as affected (PostHog advises clearing caches, but no source addresses this case directly).
What happensThe malware runs at install time; the package never has to be imported. PostHog says it searches for credentials of any kind with TruffleHog, posts them to new public GitHub repositories, and uses stolen npm tokens to infect other packages. According to Datadog, StepSecurity and Wiz, it also collects AWS/GCP/Azure cloud credentials and leaves a remote-control backdoor; according to Aikido and Datadog, it wipes the home directory if it can neither steal credentials nor spread.
What to do nowCheck lockfiles for the malicious versions. If you find one, treat the machine as fully compromised: rotate every credential from a separate clean computer, delete node_modules and the package cache, and move to PostHog's named safe versions (e.g. posthog-node 5.14.0, posthog-js 1.298.0).
Affected versionsOSV lists malicious versions of 56 PostHog packages (full list under Who is affected), mainly posthog-node 4.18.1, 5.11.3, 5.13.3; posthog-js 1.297.3; posthog-react-native 4.11.1, 4.12.5; posthog-docusaurus 2.0.6; posthog-react-native-session-replay 1.2.2; @posthog/core 1.5.6; @posthog/agent 1.24.1; @posthog/ai 7.1.2; @posthog/cli 0.5.15; @posthog/wizard 1.18.1; @posthog/nuxt 1.2.9; posthog-plugin-hello-world 1.0.1
Fixed versionsSafe versions named by PostHog: posthog-js 1.298.0, posthog-node 5.14.0, posthog-react-native 4.13.0, posthog-react-native-session-replay 1.2.3, @posthog/agent 1.24.2, @posthog/ai 7.2.0, @posthog/cli 0.5.16, @posthog/wizard 1.18.2, posthog-plugin-hello-world 1.0.0. No safe version has been named individually for the other packages (including posthog-docusaurus and @posthog/core); PostHog's post-mortem says the latest versions published after it discovered the attack are known-good.
Timeline
  • Introduced 2025-11-24 · PostHog's post-mortem says the malicious versions were published at 04:11 UTC; npm registry metadata shows posthog-node 5.11.3 published at 04:04 UTC source
  • First public 2025-11-24
  • First attacks 2025-11-24 · Malicious versions installable from npm from 04:04 UTC (npm registry; PostHog says 04:11); PostHog says they were deleted by 09:30 UTC, but npm registry shows later last-modified times for some packages; PlainCVE's judgement is that they could still be reinstalled later from lockfiles and caches (no source addresses this directly) source

Blue team playbook

Check, fix / mitigate, respond, harden. Matching the affected versions does not mean you were compromised, and a check that finds nothing does not prove you are safe; each item says what it can and cannot show. Tagged steps point to where the attack flow below can be stopped.

DETECTCheckAm I affected? Any signs of compromise?
  1. 01Check whether a project installed a malicious version

    List every package in the project's dependency tree whose name contains posthog, with its version, and compare against the table and the OSV list under Who is affected. Check lockfiles too, because later reinstalls follow them.

    Read-only check
    npm ls --all 2>/dev/null | grep -i posthog
  2. 02Search for file names published in the reports

    File names listed by PostHog and Wiz: setup_bun.js, bun_environment.js, cloud.json, contents.json, environment.json, truffleSecrets.json. Also look for an unexpected new ~/.bun/bin/bun.

    Read-only check
    find . ~ \( -name setup_bun.js -o -name bun_environment.js -o -name truffleSecrets.json -o -name cloud.json -o -name contents.json -o -name environment.json \) 2>/dev/null
  3. 03Search npm logs

    PostHog suggests looking in npm logs for entries containing "shai" or "preinstall".

    Read-only check
    grep -rniE 'shai|preinstall' ~/.npm/_logs/
  4. 04Check GitHub accounts and orgs for backdoor traces

    According to StepSecurity and Datadog, the .github/workflows/discussion.yaml backdoor workflow and the self-hosted runner named SHA1HULUD are attached to new repositories the worm creates with your token, not to your existing projects, so the thing to check is the GitHub account itself. List the account's repositories (requires an authenticated GitHub CLI; an org name works too) and look for recent public repositories you don't recognise: Datadog says their names are 18 random lowercase letters and digits and their description is "Sha1-Hulud: The Second Coming."

    Read-only check
    gh repo list YOUR_ACCOUNT --limit 100 --json name,description,createdAt,visibility
MITIGATEFix / mitigatePatch; block it if you cannot patch yet
  1. 01Move to PostHog's named safe versions and pin them

    For example posthog-js 1.298.0 and posthog-node 5.14.0. PostHog's post-mortem says the latest versions, published after it discovered the attack, are known-good.

    Read-only check
    npm view posthog-node versions --json
  2. 02Delete node_modules and clear the package cache

    GitHub's advisory for @posthog/core advises removing the package and rotating all secrets from a different computer; PostHog's issue advises deleting node_modules and running pnpm cache delete if you use pnpm; PostHog's post-mortem and GitLab's advisory for posthog-js also advise clearing the npm cache. PlainCVE's view is that this also keeps a malicious version from being reinstalled later from cache or an old lockfile.

  3. 03Turn off install scripts in CI

    The malware starts from a preinstall script; if install scripts don't run, it isn't triggered.

    Read-only check
    npm config get ignore-scripts
RESPONDRespondIf you find signs of compromise
  1. 01Treat the machine as fully compromised and rotate credentials from a clean computer

    Rotate every secret the machine could reach: GitHub PATs, npm tokens, SSH keys, cloud keys and CI secrets. Removing the package alone is not enough; the attacker may already have full access.

  2. 02Remove attacker-created repositories, runners and workflows

    Delete repositories the attacker created, remove rogue self-hosted runners and workflows, and review GitHub security logs (StepSecurity advice).

  3. 03Check whether packages you maintain were republished

    The worm uses stolen npm tokens to infect packages the victim maintains, so maintainers should check their packages for unexpected recent versions.

    Read-only check
    npm view YOUR_PACKAGE time --json
HARDENHardenPrevent the next one
  1. 01Never run PR code under pull_request_target

    Use pull_request, or split into an unprivileged pull_request job plus a privileged workflow_run job; don't rely on label gates. Require security review of workflow changes.

    Read-only check
    grep -rn 'pull_request_target' .github/workflows/
  2. 02Move to trusted publishing

    With no long-lived npm token stored in CI, a leak does far less damage. PostHog moved to this after the incident.

  3. 03Set a cooldown for new versions

    PostHog recommends a minimumReleaseAge of 3 days or more; StepSecurity suggests 2 days. This time, according to PostHog, the malicious versions were deleted after about 5 hours.

  4. 04Use phishing-resistant MFA

    Recommended by Wiz; GitHub's npm plan also requires 2FA for local publishing, with FIDO keys replacing one-time codes.

Full remediation steps and notes
  1. Check whether you installed it. Compare lockfiles and node_modules with the table and the list below it (including reinstalls from a lockfile or cache after the versions were deleted). Search for setup_bun.js, bun_environment.js, cloud.json, contents.json, environment.json and truffleSecrets.json. Look in npm logs for “shai” or “preinstall”. Also check your GitHub accounts and orgs for unexpected new repositories you don’t recognise: according to StepSecurity and Datadog, the SHA1HULUD runner and the discussion.yaml backdoor workflow are attached to these worm-created repositories.
  2. If you did, treat the machine as fully compromised. From a separate, clean computer, rotate every secret the machine could reach: GitHub PATs, npm tokens, SSH keys, cloud keys and CI secrets. Removing the package alone is not enough, because the attacker may already have full access.
  3. Clean up. Remove the package, delete node_modules, and clear the npm or pnpm cache. Delete attacker-created repositories, remove rogue runners and workflows, and review GitHub security logs. If you maintain npm packages, check them for unexpected new versions.
  4. Move to safe versions and pin them. For example posthog-node 5.14.0 and posthog-js 1.298.0 (full list in the table). The GitHub and GitLab advisories say “no patched version”, which is how those databases usually record malware; PostHog itself has published safe versions.
  5. Harden for the long term.
    • Add a cooldown for new versions: PostHog recommends a minimumReleaseAge of 3 days or more; StepSecurity suggests 2 days.
    • Turn off or restrict install scripts in CI.
    • Use phishing-resistant MFA.
    • If you maintain packages: move to trusted publishing, never run PR code under pull_request_target, and require security review of workflow changes.

PostHog had already hired a dedicated security engineer in July 2025, before the incident, and says its November incident response and post-mortem relied on the tools and practices put in place since then. After the incident, PostHog moved to trusted publishing, put workflow changes under security-team review, upgraded to pnpm 10, and redesigned how it manages GitHub secrets.

Attack flow

Read left to right: this is the order the attack happens in. A blue shield means the step can be stopped; the earlier, the better. Click an icon for details.

Read top to bottom: this is the order the attack happens in. A blue shield means the step can be stopped; the earlier, the better. Tap an icon for details.

PostHog's official npm packages hit by the Shai-Hulud 2.0 worm: a misconfigured GitHub workflow handed attackers the publish token: 1. The attacker opens a malicious pull request against PostHog's repository → 2. The attacker's code runs in the trusted CI context and steals a bot account's GitHub token → 3. The attacker uses the stolen token to change a workflow and dump every secret, including the npm publish token → 4. The attacker publishes worm-infected versions to npm → 5. On install, a preinstall script runs the malware automatically → 6. The worm uses stolen npm tokens to infect other packages the victim maintainsPostHog's official npm packages hit by the Shai-Hulud 2.0 worm: a misconfigured GitHub workflow handed attackers the publish tokenAttackerYour systemExternalThe attacker opens a malicious pull request against PostHog's repository — A workflow PostHog merged on 2025-09-11 used the pull_request_target trigger but also ran code taken from the PR itself (a reviewer-assignment script). 1The attacker opensa malicious pullrequest againstPostHog'…The attacker's code runs in the trusted CI context and steals a bot account's GitHub token — pull_request_target carries the base repository's secrets and write access, so code from the PR gets the same privileges. GitHub Security Lab calls this pattern a "pwn request". 2The attacker'scode runs in thetrusted CI contextand steals a bo…The attacker uses the stolen token to change a workflow and dump every secret, including the npm publish token — On 2025-11-23 the attacker tested the token, then used a detached commit to change the Lint PR workflow so it sent out all GitHub secrets. 3The attacker usesthe stolen tokento change aworkflow and dum…The attacker publishes worm-infected versions to npm — PostHog's post-mortem says they were published at 04:11 UTC on 2025-11-24; npm registry metadata shows posthog-node 5.11.3 at 04:04, 5.13.3 at 04:09 and 4.18.1 at 04:13. PostHog says that by 09:30 it had found and deleted the malicious packages and revoked the tokens. 4The attackerpublishesworm-infectedversions to npmOn install, a preinstall script runs the malware automatically — According to PostHog, it searches for credentials of any kind with TruffleHog and creates public repositories with the victim's token to post the data. According to Datadog and StepSecurity, it first installs the Bun runtime and then runs an obfuscated payload, also collects AWS, GCP and Azure cloud credentials, and registers a self-hosted runner named SHA1HULUD as a backdoor. 5On install, apreinstall scriptruns the malwareautomaticallyThe worm uses stolen npm tokens to infect other packages the victim maintains — According to Aikido, up to 100 packages per victim (the first wave's limit was 20). According to Aikido and Datadog, if it cannot steal credentials or spread, it tries to wipe the home directory. 6The worm usesstolen npm tokensto infect otherpackages th…Attack pathCan be stopped herePlainCVE · plaincve.date/en/vulns/mal-2025-190925-posthog-npm-shai-hulud-2 · CC BY 4.0
PostHog's official npm packages hit by the Shai-Hulud 2.0 worm: a misconfigured GitHub workflow handed attackers the publish token: 1. The attacker opens a malicious pull request against PostHog's repository → 2. The attacker's code runs in the trusted CI context and steals a bot account's GitHub token → 3. The attacker uses the stolen token to change a workflow and dump every secret, including the npm publish token → 4. The attacker publishes worm-infected versions to npm → 5. On install, a preinstall script runs the malware automatically → 6. The worm uses stolen npm tokens to infect other packages the victim maintainsPostHog's official npm packages hit bythe Shai-Hulud 2.0 worm: a misconfiguredGitHub workflow handed attackers thepublish tokenThe attacker opens a malicious pull request against PostHog's repository — A workflow PostHog merged on 2025-09-11 used the pull_request_target trigger but also ran code taken from the PR itself (a reviewer-assignment script). 1AttackerThe attacker opens amalicious pull requestagainst PostHog'srepositoryThe attacker's code runs in the trusted CI context and steals a bot account's GitHub token — pull_request_target carries the base repository's secrets and write access, so code from the PR gets the same privileges. GitHub Security Lab calls this pattern a "pwn request". 2Your systemThe attacker's code runsin the trusted CIcontext and steals a botaccount's GitHub tokenThe attacker uses the stolen token to change a workflow and dump every secret, including the npm publish token — On 2025-11-23 the attacker tested the token, then used a detached commit to change the Lint PR workflow so it sent out all GitHub secrets. 3AttackerThe attacker uses thestolen token to change aworkflow and dump everysecret, including thenpm publish tokenThe attacker publishes worm-infected versions to npm — PostHog's post-mortem says they were published at 04:11 UTC on 2025-11-24; npm registry metadata shows posthog-node 5.11.3 at 04:04, 5.13.3 at 04:09 and 4.18.1 at 04:13. PostHog says that by 09:30 it had found and deleted the malicious packages and revoked the tokens. 4AttackerThe attacker publishesworm-infected versionsto npmOn install, a preinstall script runs the malware automatically — According to PostHog, it searches for credentials of any kind with TruffleHog and creates public repositories with the victim's token to post the data. According to Datadog and StepSecurity, it first installs the Bun runtime and then runs an obfuscated payload, also collects AWS, GCP and Azure cloud credentials, and registers a self-hosted runner named SHA1HULUD as a backdoor. 5Your systemOn install, a preinstallscript runs the malwareautomaticallyThe worm uses stolen npm tokens to infect other packages the victim maintains — According to Aikido, up to 100 packages per victim (the first wave's limit was 20). According to Aikido and Datadog, if it cannot steal credentials or spread, it tries to wipe the home directory. 6ExternalThe worm uses stolen npmtokens to infect otherpackages the victimmaintainsAttack pathCan be stopped herePlainCVE · CC BY 4.0plaincve.date/en/vulns/mal-2025-190925-posthog-npm-shai-hulud-2
Free to use in reporting and teaching under CC BY 4.0. Attribution is already on the image.
  1. Attacker

    The attacker opens a malicious pull request against PostHog's repository

    A workflow PostHog merged on 2025-09-11 used the pull_request_target trigger but also ran code taken from the PR itself (a reviewer-assignment script).

    Defense: Never run outside PR code under pull_request_target; use plain pull_request for untrusted code.

  2. Your system

    The attacker's code runs in the trusted CI context and steals a bot account's GitHub token

    pull_request_target carries the base repository's secrets and write access, so code from the PR gets the same privileges. GitHub Security Lab calls this pattern a "pwn request".

    Defense: Split the job: an unprivileged pull_request job, then a privileged workflow_run job that only handles its results; give bot tokens least privilege.

  3. Attacker

    The attacker uses the stolen token to change a workflow and dump every secret, including the npm publish token

    On 2025-11-23 the attacker tested the token, then used a detached commit to change the Lint PR workflow so it sent out all GitHub secrets.

    Defense: Require security review of workflow changes; move to trusted publishing so no long-lived npm token sits in CI.

  4. Attacker

    The attacker publishes worm-infected versions to npm

    PostHog's post-mortem says they were published at 04:11 UTC on 2025-11-24; npm registry metadata shows posthog-node 5.11.3 at 04:04, 5.13.3 at 04:09 and 4.18.1 at 04:13. PostHog says that by 09:30 it had found and deleted the malicious packages and revoked the tokens.

    Defense: Use a package cooldown (e.g. pnpm's minimumReleaseAge) so new versions are adopted only after a few days.

  5. Your system

    On install, a preinstall script runs the malware automatically

    According to PostHog, it searches for credentials of any kind with TruffleHog and creates public repositories with the victim's token to post the data. According to Datadog and StepSecurity, it first installs the Bun runtime and then runs an obfuscated payload, also collects AWS, GCP and Azure cloud credentials, and registers a self-hosted runner named SHA1HULUD as a backdoor.

    Defense: Disable or restrict install scripts in CI (for example, pnpm 10 does not run dependency install scripts by default).

  6. External

    The worm uses stolen npm tokens to infect other packages the victim maintains

    According to Aikido, up to 100 packages per victim (the first wave's limit was 20). According to Aikido and Datadog, if it cannot steal credentials or spread, it tries to wipe the home directory.

    Defense: Rotate every token from a separate clean computer; maintainers should move to trusted publishing and phishing-resistant MFA.

This is not an ordinary software bug but a malicious package incident: it has no CVE ID and is not in NVD or the CISA KEV catalog. This page uses the OSV malicious-package ID MAL-2025-190925.

Who is affected

The malware runs at install time. Your code never has to import or call these packages. Treat yourself as affected if a developer machine or CI job ran npm install (or similar), pulled in one of the versions in the table or the list below it, and allowed install scripts to run. The most likely window is from 04:04 UTC on 2025-11-24 until the versions were deleted (PostHog says by 09:30 UTC). PlainCVE’s judgement is that if a lockfile already recorded a malicious version, or a local cache or internal package mirror kept a copy, a later reinstall could still bring it back, so check those too. No source addresses this case directly, but PostHog’s post-mortem and issue both advise deleting cached dependencies.

PackageMalicious versionsSafe version named by PostHogOSV ID / notes
posthog-node4.18.1, 5.11.3, 5.13.35.14.0MAL-2025-190925 (main ID for this page)
posthog-js1.297.31.298.0MAL-2025-191402; GitLab GMS-2025-554
posthog-react-native4.11.1, 4.12.54.13.0MAL-2025-190926; PostHog lists only 4.11.1; 4.12.5 appears in OSV and npm registry metadata (published 04:09 UTC, since deleted)
posthog-react-native-session-replay1.2.21.2.3MAL-2025-190649
posthog-docusaurus2.0.6Not namedMAL-2025-190924
@posthog/agent1.24.11.24.2MAL-2025-190748
@posthog/ai7.1.27.2.0MAL-2025-190644
@posthog/cli0.5.150.5.16MAL-2025-190671
@posthog/wizard1.18.11.18.2MAL-2025-190900; listed in PostHog’s issue but not the post-mortem
@posthog/core1.5.6Not namedMAL-2025-190645, GHSA-3f6v-43gc-g8fr; PostHog does not list it
posthog-plugin-hello-world1.0.11.0.0MAL-2025-190778, GHSA-9mwj-fr6x-j3hg; PostHog’s issue lists only 1.0.0, as a safe version

PostHog’s post-mortem lists only the “most relevant” packages. OSV also lists the following PostHog packages as malicious (compiled from this page’s OSV query on 2026-09-28; each package has its own MAL ID, searchable by package name on osv.dev; PostHog has not named individual safe versions for them):

  • @posthog/clickhouse 1.7.1, @posthog/hedgehog-mode 0.0.42, @posthog/icons 0.36.1, @posthog/lemon-ui 0.0.1, @posthog/nextjs-config 1.5.1, @posthog/nuxt 1.2.9, @posthog/piscina 3.2.1, @posthog/plugin-contrib 0.0.6, @posthog/plugin-server 1.10.8, @posthog/rrweb 0.0.31, @posthog/rrweb-player 0.0.31, @posthog/rrweb-record 0.0.31, @posthog/rrweb-replay 0.0.19, @posthog/siphash 1.1.2, @posthog/web-dev-server 1.0.5
  • Version 0.0.8 of these 30 plugin packages: @posthog/automatic-cohorts-plugin, @posthog/bitbucket-release-tracker, @posthog/currency-normalization-plugin, @posthog/customerio-plugin, @posthog/databricks-plugin, @posthog/drop-events-on-property-plugin, @posthog/event-sequence-timer-plugin, @posthog/filter-out-plugin, @posthog/first-time-event-tracker, @posthog/geoip-plugin, @posthog/github-release-tracking-plugin, @posthog/gitub-star-sync-plugin, @posthog/heartbeat-plugin, @posthog/ingestion-alert-plugin, @posthog/intercom-plugin, @posthog/kinesis-plugin, @posthog/laudspeaker-plugin, @posthog/migrator3000-plugin, @posthog/netdata-event-processing, @posthog/pagerduty-plugin, @posthog/plugin-unduplicates, @posthog/postgres-plugin, @posthog/sendgrid-plugin, @posthog/snowflake-export-plugin, @posthog/taxonomy-plugin, @posthog/twilio-plugin, @posthog/twitter-followers-plugin, @posthog/url-normalizer-plugin, @posthog/variance-plugin, @posthog/zendesk-plugin

npm registry metadata shows every malicious version of these 56 packages was published between 04:04 and 04:13 UTC on 2025-11-24, and none of them is in npm’s version lists any more. This page’s query covered npm packages whose names contain posthog, so it is not guaranteed to be complete.

Other points:

  • PostHog’s post-mortem says the latest versions, published after it discovered the attack, are known-good. The malicious versions were deleted from npm and replaced with newer releases; see the table for the safe versions.
  • Deletion time: PostHog says it had found and deleted the malicious packages by 09:30 UTC. However, npm registry metadata shows posthog-docusaurus last modified at 09:57 UTC and posthog-plugin-hello-world at 14:11 UTC. The registry does not record deletion times directly, so it is not certain every package was gone by 09:30.
  • Publish times: PostHog’s post-mortem says 04:11 UTC, while npm registry metadata shows posthog-node 5.11.3 published at 04:04 UTC, 5.13.3 at 04:09 UTC and 4.18.1 at 04:13 UTC. This page uses 04:04 as the start of the exposure window.
  • PostHog says it knows of no customer production deployments that used the malicious versions.
  • Installs that skip lifecycle scripts were less exposed (for example, pnpm 10 does not run dependency install scripts by default).
  • Wiz found posthog-node in 25% and posthog-js in 15% of the environments it scanned. That shows how common the packages are, not how many were infected.

How it works

There are two layers here: how PostHog itself was breached, and what the malicious package did to whoever installed it.

Layer one: a “pwn request” handed over the publish token

GitHub Actions’ pull_request_target trigger runs as the base repository, with its secrets and write access. It exists so the workflow definition comes from the trusted main branch. The trouble starts when the workflow then checks out and runs code from an outside pull request: that stranger’s code gets the same privileges. GitHub Security Lab calls this mistake a “pwn request”.

A workflow PostHog merged on 2025-09-11 did exactly that. It used pull_request_target and also ran a reviewer-assignment script taken from the PR. In its post-mortem PostHog admits its developers believed pull_request_target protected the code being run, when in fact it only controls where the workflow definition is read from.

What happened (conceptual summary):

  1. The attacker opened a malicious PR, and its code ran in the trusted CI context.
  2. That code stole a bot account’s GitHub personal access token (PAT).
  3. The attacker used the token to change a workflow and dump the repository’s secrets, including the npm publish token.
  4. The attacker used the npm token to publish worm-infected versions of the packages.

The flawed pattern (conceptual illustration, not PostHog’s actual configuration):

Show code example(yaml)
on: pull_request_target        # runs with base-repo permissions and secrets
jobs:
  assign:
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.head.sha }}  # checks out outside PR code
      - run: node scripts/assign-reviewers.js            # runs the PR's code → it gets the secrets

A safer pattern:

Show code example(yaml)
on: pull_request               # outside PRs run with no secrets and read-only access
permissions:
  contents: read
jobs:
  check:
    steps:
      - uses: actions/checkout@v4
      - run: node scripts/check.js
# Anything that needs write access goes in a separate workflow triggered by workflow_run,
# which only handles the results and never runs PR code

Layer two: what the Shai-Hulud 2.0 worm did

This summary comes from the research reports. This page did not open any package file or malware sample.

  • Starts at install: a preinstall script (setup_bun.js) installs the Bun JavaScript runtime, then runs a large obfuscated program, bun_environment.js. Datadog notes that using Bun helps it avoid tools that only watch Node.js.
  • Steals credentials: PostHog says it uses the open-source TruffleHog scanner to search the environment for credentials of any kind. According to Datadog and StepSecurity, it also collects GitHub tokens, npm tokens, environment variables, and AWS, GCP and Azure cloud credentials and secret-manager contents; Wiz counted hundreds of cloud credentials among those leaked across the whole wave.
  • Posts them publicly: it creates new public GitHub repositories with the victim’s own token and posts the stolen data there. Wiz also saw one victim’s secrets posted under unrelated users’ accounts.
  • Leaves a backdoor: it registers a self-hosted GitHub Actions runner named SHA1HULUD and adds .github/workflows/discussion.yaml. That workflow listens for new GitHub Discussions, so the attacker can run commands on the machine registered as that runner just by creating a new Discussion in the victim’s repository with the command in its body.
  • Spreads itself: with a stolen npm token it infects up to 100 packages the victim maintains (according to Aikido, the first wave’s limit was 20).
  • Destroys data: according to Aikido and Datadog, if it can neither steal credentials nor spread, it tries to wipe the user’s home directory. StepSecurity also describes Linux file overwriting meant to erase evidence.
  • Other behaviour: it runs immediately in CI but in the background on developer machines, and it tries to escape Docker containers through privileged mounts.

This maps to CWE-506 (Embedded Malicious Code).

Timeline

When (UTC)Event
2025-09-11PostHog merges the vulnerable workflow change
2025-09-14 or 09-16First Shai-Hulud wave (GitHub dates it the 14th, Aikido the 16th)
2025-11-18 5:40 PM (the post-mortem gives no time zone; its screenshots are in UTC+2, so this may not be UTC)Attacker account opens the malicious PR; bot PAT stolen
2025-11-23 15:28Attacker deletes a workflow run to test the token
2025-11-23 15:43–15:45Attacker uses a detached commit to change the Lint PR workflow; all GitHub secrets, including the npm token, are sent out
2025-11-24 ~03:16Second wave, “Sha1-Hulud: The Second Coming”, begins, with AsyncAPI hit first (Aikido)
2025-11-24 04:04–04:13Malicious PostHog versions published to npm (npm registry: posthog-node 5.11.3 at 04:04, 5.13.3 at 04:09, 4.18.1 at 04:13, most other PostHog packages at 04:08–04:10; PostHog’s post-mortem says 04:11)
2025-11-24 09:30PostHog says it had found and deleted the malicious packages and revoked tokens by this time
2025-11-24 09:57, 14:11npm registry last-modified times for posthog-docusaurus and posthog-plugin-hello-world (the registry does not record deletion times directly)
2025-11-24PostHog opens GitHub issue #2633; GitHub publishes the @posthog/core advisory; OSV publishes the posthog-node record MAL-2025-190925 at 16:31
2025-11-25OSV publishes the posthog-js record; GitLab publishes GMS-2025-554
2025-11-26PostHog publishes its post-mortem
CVE / KEVNone; not applicable

Further reading

  • Scoring: there is no CVE and no NVD score. GitHub’s advisory for @posthog/core gives no CVSS number and rates it Critical (Malware). GitLab gives posthog-js a CVSS 3.1 score of 8.6 (AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H). Because that score is for a different package, this page leaves its severity field empty.
  • Where sources disagree:
    • Scope of affected packages: PostHog’s post-mortem lists only the 8 “most relevant” packages (10 versions) and its issue adds @posthog/wizard, for 9 in total; OSV lists 56 PostHog packages, including @posthog/core, posthog-react-native 4.12.5 and posthog-plugin-hello-world.
    • Deletion time: PostHog says by 09:30 UTC; npm registry shows last-modified times of 09:57 UTC for posthog-docusaurus and 14:11 UTC for posthog-plugin-hello-world.
    • Publish time of the malicious versions: PostHog’s post-mortem says 04:11 UTC, but npm registry metadata shows posthog-node 5.11.3 was already published at 04:04 UTC.
    • Size of the whole wave: Aikido counts 492 packages; Datadog counts 796 and calls that a lower bound; Wiz estimates about 700 to 800, and also counted 775 exposed GitHub tokens plus 373 AWS, 300 GCP and 115 Azure credentials; StepSecurity’s early count was more than 70; PostHog’s issue says “over 300”.
    • Repository description strings: StepSecurity and Aikido report “Sha1-Hulud: The Second Coming”. Wiz separately reports a potential second phase first seen at 22:45 UTC on 11/25, in which one victim’s private repositories were made public using credentials stolen in phase one; the description used for that phase is “Sha1-Hulud: The Continued Coming”.
    • Wiz says the second wave reuses the first wave’s name and methods but may be run by different people.
  • Where PostHog fits in: PostHog was breached through its own CI misconfiguration, not because a maintainer installed an infected package. Datadog calls this one of several ways the attackers got in. Once the malicious versions were published, PostHog’s packages became carriers that could infect other maintainers.
  • npm context: the attack came shortly before npm’s 2025-12-09 deadline for revoking classic tokens. GitHub’s September 2025 plan includes retiring classic tokens, granular tokens that last at most 7 days, trusted publishing, and required 2FA for local publishing with FIDO keys replacing one-time codes.
  • AI angle: two affected packages are named @posthog/ai and @posthog/agent, but no source links the attack itself to AI, and this page does not suggest one.
  • Could not be confirmed: whether GitHub has GHSA advisories for posthog-node and posthog-js; how many times the malicious versions were downloaded while live; what CISA’s alert on the first Shai-Hulud wave says (the page could not be read and is not used here).

Verification Checked item by item against sources by AI under our review rules, all passed; not human-reviewed and not yet reproduced in our lab

AI review2026-09-30 · review rules

Verification records describe the environment and the result only, never reproduction steps or code that could attack other people’s systems. See our policy.

Sources

  1. VendorShai-Hulud 2.0 Supply Chain Attack Post-Mortem · PostHog, 2025-11-26 · accessed 2026-09-28
  2. Otherposthog-node package metadata (npm registry) · npm · accessed 2026-09-28
    time field: 5.11.3 published 2025-11-24T04:04:24Z, 5.13.3 at 04:09:08Z, 4.18.1 at 04:13:22Z
  3. Advisoryposthog-js and other packages were temporarily compromised - November 24th - SHA1 HULUD (issue #2633) · PostHog (GitHub), 2025-11-24 · accessed 2026-09-28
  4. AdvisoryMAL-2025-190925: Malicious code in posthog-node (npm) · OSV, 2025-11-24 · accessed 2026-09-23
  5. AdvisoryMAL-2025-190926: Malicious code in posthog-react-native (npm) · OSV, 2025-11-24 · accessed 2026-09-28
    Malicious versions 4.12.5 and 4.11.1
  6. AdvisoryMAL-2025-190900: Malicious code in @posthog/wizard (npm) · OSV, 2025-11-24 · accessed 2026-09-28
  7. AdvisoryMAL-2025-190924: Malicious code in posthog-docusaurus (npm) · OSV, 2025-11-24 · accessed 2026-09-28
  8. AdvisoryMAL-2025-190778: Malicious code in posthog-plugin-hello-world (npm) · OSV, 2025-11-24 · accessed 2026-09-28
  9. AdvisoryMalware in posthog-plugin-hello-world (GHSA-9mwj-fr6x-j3hg) · GitHub Advisory Database, 2025-11-24 · accessed 2026-09-28
  10. AdvisoryMAL-2025-190753: Malicious code in @posthog/siphash (npm) · OSV, 2025-11-24 · accessed 2026-09-28
  11. AdvisoryMAL-2025-191292: Malicious code in @posthog/icons (npm) · OSV, 2025-11-25 · accessed 2026-09-28
  12. AdvisoryMAL-2025-190887: Malicious code in @posthog/nextjs-config (npm) · OSV, 2025-11-24 · accessed 2026-09-28
  13. AdvisoryMAL-2025-190749: Malicious code in @posthog/nuxt (npm) · OSV, 2025-11-24 · accessed 2026-09-28
  14. AdvisoryOSV search results for npm packages matching posthog · OSV · accessed 2026-09-28
    This page queried the OSV API on 2026-09-28 for each npm package whose name contains posthog and found MAL records for 56 PostHog packages
  15. Otherposthog-docusaurus package metadata (npm registry) · npm · accessed 2026-09-28
    time field: 2.0.6 published 2025-11-24T04:08:49Z; modified 2025-11-24T09:57:58Z; 2.0.6 is no longer in the version list
  16. Otherposthog-react-native package metadata (npm registry) · npm · accessed 2026-09-28
    time field: 4.12.5 published 2025-11-24T04:09:10Z, 4.11.1 at 04:10:52Z; neither is in the version list any more
  17. Otherposthog-plugin-hello-world package metadata (npm registry) · npm · accessed 2026-09-28
    time field: 1.0.1 published 2025-11-24T04:08:55Z; modified 2025-11-24T14:11:50Z; 1.0.1 is no longer in the version list
  18. AdvisoryMAL-2025-191402: Malicious code in posthog-js (npm) · OSV, 2025-11-25 · accessed 2026-09-23
  19. AdvisoryMAL-2025-190645: Malicious code in @posthog/core (npm) · OSV, 2025-11-24 · accessed 2026-09-23
  20. AdvisoryMalware in @posthog/core (GHSA-3f6v-43gc-g8fr) · GitHub Advisory Database, 2025-11-24 · accessed 2026-09-28
  21. AdvisoryAdvisories for Npm/Posthog-Js package · GitLab Advisory Database · accessed 2026-09-23
  22. Advisoryposthog-js contains malware after npm account takeover (GMS-2025-554) · GitLab Advisory Database, 2025-11-25 · accessed 2026-09-28
  23. ResearchShai-Hulud 2.0: Ongoing Supply Chain Attack · Wiz, 2025-11-24 · accessed 2026-09-28
  24. ResearchSha1-Hulud: The Second Coming – Zapier, ENS Domains, and Other Prominent NPM Packages Compromised · StepSecurity, 2025-11-23 · accessed 2026-09-28
  25. ResearchShai-Hulud 2.0 npm worm analysis · Datadog Security Labs, 2025-11-25 · accessed 2026-09-28
  26. ResearchShai Hulud strikes again, hitting Zapier & ENSDomains · Aikido Security, 2025-11-24 · accessed 2026-09-23
  27. ResearchKeeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests · GitHub Security Lab, 2021-08-03 · accessed 2026-09-23
  28. VendorOur plan for a more secure npm supply chain · GitHub Blog, 2025-09-22 · accessed 2026-09-23

Press brief

In one sentence

On November 24, 2025, the official JavaScript packages of analytics company PostHog were replaced on npm with malicious versions carrying a self-spreading worm called "Sha1-Hulud: The Second Coming" (Shai-Hulud 2.0). Attackers first abused a misconfigured GitHub automation workflow at PostHog to steal its publish token, then released versions that steal developer credentials at install time. PostHog says the malicious versions were deleted about five hours later and that it knows of no customer production deployments that used them. It was part of a wider wave that hit hundreds of npm packages.

Key facts

Malicious versions published
2025-11-24 04:11 UTC (PostHog post-mortem); npm registry metadata shows posthog-node 5.11.3 at 04:04 UTC and 5.13.3 at 04:09 UTC; PostHog says they were deleted by 09:30 UTC posthog.com
PostHog packages OSV lists as malicious
56 (this page's OSV query on 2026-09-28; may be incomplete) osv.dev
Incident ID for this page
MAL-2025-190925 (posthog-node 4.18.1, 5.11.3, 5.13.3) osv.dev
Safe versions
posthog-node 5.14.0, posthog-js 1.298.0 and others github.com
Classification
GitHub's advisory for @posthog/core (GHSA-3f6v-43gc-g8fr) lists CWE-506 Embedded Malicious Code and rates it Critical (Malware); GitLab's GMS-2025-554 for posthog-js also lists CWE-506 github.com
Score
No CVE or NVD score; GitLab gives posthog-js CVSS 3.1 8.6 advisories.gitlab.com

Confirmed

  • Several official PostHog npm packages were published on 2025-11-24 in versions containing the Shai-Hulud 2.0 worm.
  • The breach began with a PostHog GitHub Actions workflow that used pull_request_target while running code from the PR.
  • The malware runs at install time through a preinstall script, steals credentials and tries to infect more packages.
  • PostHog says it knows of no customer production deployments that used the malicious versions.

Unconfirmed / disputed

  • PostHog has not published a complete list of affected packages. Its post-mortem lists only the 8 "most relevant" packages (10 versions) and its issue adds @posthog/wizard, for 9 in total, while OSV lists 56 PostHog packages as malicious (compiled from this page's 2026-09-28 query; may still be incomplete).
  • Whether malicious versions could be reinstalled after deletion from a lockfile, local cache or internal mirror: PostHog and GitLab advise clearing caches, but no source addresses this directly; treating it as affected is PlainCVE's judgement.
  • When each package was actually deleted from npm: PostHog says by 09:30 UTC, but npm registry shows posthog-docusaurus last modified at 09:57 UTC and posthog-plugin-hello-world at 14:11 UTC; the registry does not record the deletion time directly.
  • Estimates of the whole wave differ: from about 490 to about 800 packages.
  • No source gives how many times the malicious PostHog versions were downloaded while live.
  • Most packages, including posthog-docusaurus and @posthog/core, have no individually named official safe version.

Quotable line

What was breached wasn't PostHog's product, but the automated pipeline that ships it.

Images

Download the share image (PNG) · Attack flow diagram (downloadable)

Please credit "PlainCVE" and link to this page. Full citation format is under "How to cite" below. Found a factual error? Report it.

How to cite this page

This article is CC BY 4.0. Please keep the attribution and link when republishing.

PlainCVE Team (2026). "PostHog's official npm packages hit by the Shai-Hulud 2.0 worm: a misconfigured GitHub workflow handed attackers the publish token". PlainCVE. https://plaincve.date/en/vulns/mal-2025-190925-posthog-npm-shai-hulud-2 (accessed YYYY-MM-DD)
BibTeX
@misc{mal2025190925posthognpmshaihulud22026,
  title  = {PostHog's official npm packages hit by the Shai-Hulud 2.0 worm: a misconfigured GitHub workflow handed attackers the publish token},
  author = {PlainCVE Team},
  year   = {2026},
  howpublished = {PlainCVE},
  url    = {https://plaincve.date/en/vulns/mal-2025-190925-posthog-npm-shai-hulud-2},
  note   = {Updated 2026-09-30}
}