npm Supply Chain Worm Hits @7nohe/openapi-react-query-codegen

Date Observed: August 28, 2026
Ecosystem: npm, with propagation capabilities targeting PyPI and RubyGems
Targets: Developers and CI runners installing @7nohe/openapi-react-query-codegen; maintainer, cloud, and CI/CD credentials
Attack Type: Self-propagating software supply chain worm (Shai-Hulud “Trinitite” variant); source package compromised via CI/CD workflow abuse
Impact: Credential theft, cross-ecosystem propagation capability, and a destructive token-monitor trap

Key Takeaways

     

      • The source package was published by its own release workflow after an outside account commented “npm publish” on a pull request.

      • Downstream spread did not reuse that flaw; the worm republished packages directly with stolen maintainer tokens.

      • The worm used a malicious binding.gyp execution path that can execute even with –ignore-scripts. Later releases also added a preinstall hook.

      • The payload was a Shai-Hulud worm variant, “Trinitite,” that stole credentials and contained propagation mechanisms targeting npm, PyPI, and RubyGems.

      • Last known-good versions are 0.5.3, 1.6.2, 2.2.0, and 3.0.2; a token monitor punishes early credential rotation.

    Introduction

    On August 28, 2026, ten compromised versions of @7nohe/openapi-react-query-codegen, a TanStack Query code generator that draws roughly 150,000 weekly downloads, appeared on npm within about 20 minutes. Eight of these releases carried a self-propagating worm, a variant of the original Shai-Hulud npm worm that its authors labeled “Trinitite: Sponsored by Preview 2 Effects.” It stole developer and CI/CD credentials and included mechanisms to propagate poisoned packages across npm, PyPI, and RubyGems.

    The source package was compromised through a flaw in its own release workflow, but the spread that followed did not depend on that flaw. Once the worm held a maintainer’s tokens, it republished trojanized packages under other namespaces directly.

    Scope and Impact of the Attack

    Ten versions were pushed in a roughly 20-minute window between 20:00 and 20:21 UTC: 0.5.4, 0.5.5, 1.6.3, 1.6.4, 2.2.1, 2.2.2, 3.0.3, and 3.0.4, plus two prerelease builds. The last known-good releases are 0.5.3, 1.6.2, 2.2.0, and 3.0.2.

    Every malicious release carried a valid npm provenance attestation. Because the release workflow itself was subverted, that attestation only proved the code ran inside the repository’s CI, not that it was legitimate. Installing one of the worm-bearing versions could execute the malware during package installation. Researchers later found nine public GitHub repositories carrying the malware’s signatures, evidence the propagation stage worked as designed.

    How the Attack Works

    Stage 0: How the source package was published

    The project’s release.yml workflow treated any pull-request comment reading exactly npm publish as a signal to build and publish. It had three compounding flaws: no check on the commenter’s association with the repository, a checkout of the untrusted pull-request head, and id-token: write permission for npm Trusted Publishing. An external account opened pull requests #215 and #216 from a fork, posted the trigger phrase, and the workflow checked out the fork’s code and minted a short-lived OIDC publishing token. No npm credential was needed. This is the same class of trigger abuse seen in GitHub’s “pwn request” misconfiguration, and it explains only this one package. The rest of the campaign ran on stolen tokens.

    Stage 1: Install-time execution through binding.gyp

    The attack shipped in two waves. The first four versions carried only a malicious binding.gyp. During node-gyp rebuild, it walked Python’s class tree to catch_warnings, reached __builtins__, and ran os.system(‘node 3FWCvzduYZg.js’). This path can execute even when npm scripts are disabled with –ignore-scripts. Twenty minutes later, the second wave added a plain preinstall hook pointing at the same file, a more conventional malicious install-time script that widened the number of installs it would trigger.

    Stage 2: The staged loader

    3FWCvzduYZg.js is a single-line, 4-6 MB loader wrapped in XOR encoding over an AES-128-GCM payload. On execution it downloaded the Bun 1.4.0 runtime from GitHub release assets into a temporary directory named trinnyyyy-*, then ran the worm under Bun to sidestep Node-based tooling and analysis.

    Propagation and Persistence

    The worm harvested GitHub, npm, PyPI, RubyGems, and Artifactory tokens, cloud credentials from AWS, Azure, and GCP, Vault and Kubernetes material, SSH keys, and GitHub Actions secrets. It queried cloud-metadata endpoints such as 169.254.169.254 and metadata.google.internal for instance credentials. It then used those tokens to spread: republishing trojanized packages under victim namespaces, and creating public GitHub repositories to hold layered, encrypted archives of the stolen data committed under the victim’s own token. It also rewrote CI workflows to leak secrets and injected hooks into AI developer tools, including Claude Code, Cursor, and Codex configurations, so the payload re-ran on the next session. A persistent service polled GitHub once a minute; a 40x response, the kind returned after a token is revoked, triggered a destructive cleanup routine, according to JFrog. That trap is why responders are told to isolate the host and remove the monitor before rotating any credentials.

    Why this Matters to DevOps and DevSecOps Teams

    The headline risk is not one bad package; it is a worm that turns any set of stolen build credentials into new malicious releases across multiple ecosystems. A single compromised install can hand an attacker the tokens to publish under your organization’s namespaces, rewrite your CI workflows, and reach every registry your pipeline can push to. This is the same worm family behind the TeamPCP compromise of npm packages, now with quieter install-time execution.

    The entry point carries a separate lesson. The source project published from CI on an untrusted pull-request comment. Any repository that publishes on issue_comment or a similar low-privilege event, checks out untrusted pull-request code, and holds publishing permissions is exposed to the same initial compromise.

    Why Provenance and Script Blocking Aren’t Enough

    Provenance and trusted publishing were meant to raise the bar, and here they signed the malware. Attestation-trusting signature and SBOM checks see a valid, repo-issued credential and pass it. Disabling npm lifecycle scripts with –ignore-scripts is not sufficient because binding.gyp executes through node-gyp, not npm scripts. Endpoint tools on laptops can miss the activity because the worm runs under a freshly downloaded Bun binary and exfiltrates to github.com, a domain commonly allowed by egress policies. The malicious versions were live for hours and consumed before any human reviewed them.

    How InvisiRisk Protects Against This Attack

    InvisiRisk Build Application Firewall (BAF) is a network-level proxy that enforces policy on build-time behavior. Two controls map directly to this attack.

    Block freshly published versions with the Stability Buffer. The compromised versions, including eight releases carrying the worm, appeared within about 20 minutes of publication. InvisiRisk blocks newly published packages for a configurable window (default 48 hours) before a build can pull them, blocking freshly weaponized releases during exactly this kind of short, high-velocity window.

    Block the exfiltration with Unauthorized API Action Enforcement. The worm exfiltrated stolen credentials by creating a GitHub repository under the victim’s token and writing the stolen data to it. InvisiRisk enforces policy on outbound API calls during a build and blocks actions outside expected build scope, such as creating repositories or pushing data, so the exfiltration step is stopped even after malicious code has started running.

    Why Build-Time Defenses Matter

    The attack demonstrates how much damage can occur during the build process: it was published from CI, executed during install, and exfiltrated from the runner before any human saw the code. Signatures and provenance can establish where and how a package was built, but they do not determine what code is allowed to do while it executes during a build. Blocking newly published releases and preventing out-of-scope publishes and write actions are controls that could have contained key stages of this attack.