Posthog: npm installs risked secret exfiltration for 5 hours
PostHog’s npm SDKs were briefly trojanized by the Shai-Hulud v2 worm. Here’s what happened, how they fixed it, and lessons for defending CI/CD at scale.
Company and product
PostHog is an open-source product analytics platform with SDKs for web and mobile apps. Its JavaScript packages are widely embedded and frequently updated, which makes its npm footprint both critical and high-velocity. On November 24, 2025, PostHog faced its most impactful security incident to date: a short-lived compromise of several npm releases by the Shai-Hulud 2.0 supply-chain worm.
The worm affected not only Posthog but many other companies, including Postman and Zapier. Wiz.io published an overview of the incident.
What happened
At 04:11 UTC on Nov 24, malicious versions of multiple PostHog JavaScript packages were published to npm. These builds included a preinstall script that scanned the local environment for secrets using TruffleHog, exfiltrated any found credentials to a new public GitHub repo, and used stolen npm tokens to publish further malicious packages, allowing the worm to self-propagate. PostHog removed the bad releases and revoked tokens the same morning.
Timeline
- Initial access (Nov 18, ~17:40 local / 15:40 UTC): An attacker opened and quickly closed a PR from a throwaway account that tweaked a script run by
pull_request_targetin GitHub Actions. That change executed untrusted code and exfiltrated a bot’s GitHub PAT from CI. With that PAT, the attacker later wrote to repos and accessed additional CI secrets. - Privilege expansion (Nov 23, ~15:28–15:45 UTC): Using the stolen PAT, the attacker deleted a workflow run (likely a validity test) and then pushed a detached commit that modified a workflow to dump all GitHub secrets, including PostHog’s npm publishing token.
- Malicious publish (Nov 24, 04:11 UTC): Compromised npm versions went live, starting the worm’s spread via
preinstall. Detection & containment by 09:30 UTC: PostHog identified and removed the packages, revoked credentials, and began broad secret rotation.
TTD (Time to Detect): ~5h19m (04:11 → 09:30 UTC).
TTR (Time to initial remediation): ~5h19m to remove malicious packages and revoke tokens; additional hardening and rotations continued thereafter
Who was affected
Users installing specific PostHog JavaScript packages from npm during the window between 04:11 and 09:30 UTC on Nov 24. Users of the “script” tag (browser CDN snippet) were not affected because the worm relied on npm lifecycle hooks.
How PostHog responded
The team removed trojanized releases from npm, revoked publishing tokens, and preemptively rotated potentially exposed credentials. They then hardened their supply-chain controls by moving to npm’s Trusted Publisher model, increasing review requirements for any change to workflow files, upgrading to pnpm 10 (disabling install scripts and enabling minimumReleaseAge), and reworking GitHub secrets management for faster, safer incident handling.
How PostHog communicated
PostHog published a candid, technically detailed post-mortem two days later, on November 26. The write-up clearly lists affected packages and versions, precise UTC timestamps, containment steps, and actionable guidance for customers (file indicators to search for, npm log queries, cache clean-ups, and pinning to known-good versions). This level of specificity helped downstream teams verify exposure quickly and recover with minimal guesswork.
Key learnings for other teams
- Treat
pull_request_targetas hazardous by default. It runs with the target repo’s privileges; if your workflow checks out PR-controlled code (directly or via helper scripts), you’ve created an RCE bridge into CI. Restrict checkout to safe refs, avoid executing repo scripts for untrusted PRs, and require human approval gates for any workflow that would do so. - Minimize and scope machine credentials. Replace broad PATs with short-lived, least-privilege tokens (e.g., GitHub OIDC → cloud/registry-scoped creds; npm Trusted Publisher instead of long-lived tokens). A single high-privilege PAT enabled write access and secret exfiltration here.
- Harden package consumption paths. Enforce a minimum release age (e.g., 72 hours) in package managers, mirror and vet dependencies, and disable lifecycle hooks where possible. These controls blunt fast-moving supply-chain worms like Shai-Hulud v2 seen across large ecosystems.
- Instrument CI for rapid forensics. Turn on comprehensive audit logs, preserve workflow run artifacts, and alert on high-risk events: force-pushes, detached commits to protected branches, workflow file edits, token creation/usage, and unusual repo creations in your org. PostHog’s ability to reconstruct attacker actions depended heavily on GitHub audit trails.
- Secure “helper” scripts. Moving logic into repo scripts like
assign-reviewers.jsis convenient but risky. Prefer declarative workflows or reviewed, version-pinned actions; never run PR-controlled code in privileged jobs. - Practice supply-chain incident drills. Pre-stage playbooks for: revoking registry tokens, yanking versions, notifying users with exact version lists, and mass-rotating secrets. Worms that auto-publish can amplify quickly; minutes matter.
Quick summary
The incident stemmed from a GitHub Actions workflow configured with pull_request_target, which executed code from an external pull request and leaked a bot’s personal access token. With that token, the attacker pulled additional CI secrets, including npm publishing credentials, and pushed malicious PostHog package versions to npm that ran a preinstall script to exfiltrate secrets on machines that installed them. The impact was confined to PostHog’s JavaScript packages for roughly five hours on November 24, 2025; users loading PostHog via the browser <script>/CDN snippet were not affected. PostHog responded by yanking the compromised releases, revoking and rotating credentials, moving to npm’s Trusted Publisher model, tightening review controls for workflow changes, upgrading to pnpm 10 with safer defaults, and overhauling secrets management.
How ilert can help
Incidents like this are a race. ilert helps compress every minute between signal and action.
- Smart alerting and on-call. Route npm/advisory feeds, GitHub audit anomalies, and registry telemetry to the right engineer instantly with escalation chains.
- Automations (ilert Alert Actions). From any alert, run one-click actions via your outbound integrations: post to Slack or Microsoft Teams, open or update a Jira ticket, create a GitHub issue, trigger a webhook or AWS Lambda to disable npm tokens or pause a pipeline, and update your status page. Such automations significantly improve the speed of incident resolution.
- Stakeholder comms. ilert can spin up a private, invite-only war room from the alert so only the right security, engineers, legal, and comms folks see sensitive details. It targets notifications to impacted customers first through your connected channels (email, Slack/Teams, ticketing) with clear “what happened / who’s affected / what to do now” guidance. Once containment is done, ilert can generate pre-approved, AI-assisted status page drafts that auto-insert versions and UTC timelines for fast, accurate publishing.
- Postmortems: With ilert AI, create structured post-incident documents to capture root causes, timelines, TTD/TTR, and preventive actions, turning hard-won lessons into durable practice.
