Feed
CriticalPublished 28 Aug 2026Updated 29 Aug 20261 package · 10 versions

`@7nohe/openapi-react-query-codegen` compromised - "Trinitite" (Mini Shai-Hulud continuation) worm published 10 poisoned versions via a fork PR + `issue_comment` trigger without an author-association gate

Summary

On 2026-08-28 an attacker published 10 malicious versions of @7nohe/openapi-react-query-codegen (~150K npm weekly downloads) in a ~20-minute window. Root cause: an issue_comment-triggered GitHub Actions release workflow without an author-association gate - any GitHub user could open a fork PR, comment npm publish, and the pipeline checked out the fork, ran pnpm install (executing the attacker's preinstall hook) with id-token: write and minted an npm OIDC trusted-publishing token to ship the poisoned versions. Payload is a "Trinitite: Sponsored by Preview 2 Effects" credential-stealer worm continuing the Mini Shai-Hulud / TeamPCP lineage.

wormcredential-theftci-cd-compromiseaccount-takeovermaintainer-takeover
Threat actor
TeamPCP (attributed) - "Trinitite" campaign
Detected by
Aikido Security · SafeDep · GitHub Advisory Database
Also known as
Trinitite · Trinitite: Sponsored by Preview 2 Effects · Mini Shai-Hulud (openapi-react-query-codegen wave) · openapi-react-query-codegen 2026-08-28 compromise
Ecosystems
npm
Packages tracked
1

What happened

On 2026-08-28 an attacker published 10 malicious versions of the popular npm package @7nohe/openapi-react-query-codegen (a TanStack Query codegen against OpenAPI schemas, ~150K npm weekly downloads) within a ~20-minute window. The payload is a self-propagating credential-stealing worm calling itself Trinitite: Sponsored by Preview 2 Effects - a direct continuation of the TeamPCP / Mini Shai-Hulud lineage tracked in Shai-Hulud (2025-09), Mini Shai-Hulud Wave 2 (2026-04-30), and Mini Shai-Hulud on AntV (2026-05-19).

Affected versions

| Version | Note | |---|---| | 0.5.4, 0.5.5 | Adjacent pair on the 0.5 release train | | 1.6.3, 1.6.4 | Adjacent pair on the 1.6 release train | | 2.2.1, 2.2.2 | Adjacent pair on the 2.2 release train | | 3.0.3, 3.0.4 | Adjacent pair on the 3.0 release train | | 0.0.0-365d4eb738d3146583431948d3ba6e27a32556be | Commit-hash prerelease tag | | 0.0.0-ec7876d6c917dad516ba69bbfafc948b834bf0ab | Commit-hash prerelease tag |

Pairing an adjacent malicious version onto every major release train is the noteworthy detail: it catches consumers pinned to ^0.5, ^1.6, ^2.2 and ^3.0 alike, not just those tracking latest.

Root cause: unguarded issue_comment release trigger

The repository's release workflow was wired to fire on issue_comment events with types: [created] and no if: guard on the commenter. That is enough:

  1. Fork PR: attacker opens a pull request from a fork of 7nohe/openapi-react-query-codegen containing an arbitrary code change (in this case, a malicious preinstall hook added to package.json).
  2. Trigger comment: the attacker leaves the comment npm publish (or any similar keyword the workflow keys on) on that pull request. GitHub delivers an issue_comment event; the workflow fires.
  3. Fork checkout: the workflow checks out github.event.pull_request.head.sha - the attacker's fork, complete with its malicious preinstall hook.
  4. Preinstall runs: pnpm install (or npm install) executes the attacker-controlled preinstall. Because the workflow granted permissions: id-token: write at workflow scope for the trusted-publishing step, that permission is already in the environment when the preinstall runs.
  5. Mint OIDC token: the preinstall exchanges the workflow's OIDC identity for an npm publish token via the npm trusted-publishing endpoint.
  6. Publish: the preinstall then invokes npm publish ten times, once for each of the poisoned versions, all landing on the legitimate @7nohe/openapi-react-query-codegen package identity.

The maintainer's npm credentials were never touched. The trust chain that broke is the assumption that only maintainers can trigger the workflow, and that only maintainer-authored code will ever be checked out inside it.

Payload: "Trinitite" worm

On postinstall of any consumer install, the payload:

  • Reads local npm auth from ~/.npmrc, ~/.config/pnpm/rc, and NPM_TOKEN environment variables.
  • Reads local GitHub auth (gh CLI cache, GH_TOKEN, GITHUB_TOKEN, ~/.config/gh/hosts.yml).
  • Enumerates every npm package the current user has publish rights on via npm access ls-packages.
  • For each such package, attempts to publish a trojanised version carrying the same worm payload - the self-propagation step.
  • Creates a new attacker-controlled GitHub repository under whichever account the stolen token can reach, named <Touhou-character-name>-<Touhou-character-name>-<random-number> with the description set literally to Trinitite: Sponsored by Preview 2 Effects.
  • Commits the harvested credentials to that repository as results/doubletrinnys-{counter}-{timestamp}.json - an encrypted credential bundle plus its encryption key, mirroring the "double" naming used across earlier Shai-Hulud iterations.

Scope of downstream impact

Only @7nohe/openapi-react-query-codegen under the @7nohe scope shows the anomalous publish burst; other scoped packages did not receive suspicious publishes on 2026-08-28. Downstream: any consumer that installed a poisoned tarball between first publish and npm takedown - developer workstations and CI runners alike - must be treated as fully compromised, with every credential in reach of that environment rotated from a clean host.

Related tracked activity

Affected packages (1)

These are usually pulled in as transitive dependencies rather than installed directly. Check your whole tree at once - it runs in your browser and nothing is uploaded.

Impact

  • Ten malicious @7nohe/openapi-react-query-codegen versions shipped to the npm public registry on 2026-08-28 within a ~20-minute window: 0.5.4, 0.5.5, 1.6.3, 1.6.4, 2.2.1, 2.2.2, 3.0.3, 3.0.4, plus two pre-release commit-hash tags 0.0.0-365d4eb738d3146583431948d3ba6e27a32556be and 0.0.0-ec7876d6c917dad516ba69bbfafc948b834bf0ab. The attacker paired each malicious release with an adjacent version across every major release train (0.5.x, 1.6.x, 2.2.x, 3.0.x) so that any consumer pinned to ^0.5, ^1.6, ^2.2, or ^3.0 at install time between 2026-08-28 publish and takedown resolved a compromised tarball
  • The upstream package is a code generator for TanStack Query (formerly React Query) hooks against an OpenAPI schema; ~150,000 npm weekly downloads at the time of compromise. Consumers are React / TypeScript teams generating typed API clients at build time - a population that resolves the package on every pnpm install / yarn install / npm install in CI
  • Attack chain: (1) attacker opened a pull request from a fork against 7nohe/openapi-react-query-codegen, (2) commented npm publish on the PR, (3) the repository's release workflow triggered on issue_comment with types: [created] fired without an if: gate on github.event.comment.author_association - so any GitHub user with a comment permission could invoke it, (4) the workflow checkouted the head SHA (the attacker's fork), (5) ran pnpm install which executed the attacker's preinstall hook, (6) held permissions: id-token: write for npm trusted publishing via OIDC, so the attacker's preinstall minted a short-lived npm publish token in-workflow, (7) used that token to publish 10 tarballs under the legitimate @7nohe/openapi-react-query-codegen identity
  • Payload behaviour ("Trinitite"): on postinstall, enumerates local npm auth (~/.npmrc, NPM_TOKEN) and every npm package the current user maintains, then attempts to publish trojanised versions of each of those packages using the harvested token - i.e. the worm self-propagates in the same shape as Shai-Hulud (2025-09) and Mini Shai-Hulud Wave 2 (2026-04-30). Exfiltrated credentials are committed to attacker-controlled GitHub repositories whose names combine two randomly-chosen Touhou Project character names plus a random number, with the repo description set literally to Trinitite: Sponsored by Preview 2 Effects, and each payload lands as results/doubletrinnys-{counter}-{timestamp}.json containing an encrypted credential bundle and its encryption key
  • Impact for downstream teams: any CI or developer workstation that ran pnpm install / yarn install / npm install for a project pinned to any of the 10 affected versions between 2026-08-28 first publish and npm takedown should be treated as fully compromised: npm tokens, GitHub tokens surfaced in the environment, and any other secrets present in the shell / CI runner context are considered exfiltrated. If the affected machine had publish rights to other packages under the developer's npm account, those packages may already carry their own trojanised republishes - the worm shape is the whole point
  • Only the @7nohe/openapi-react-query-codegen package under the @7nohe npm scope shows the anomalous publish burst; other packages under the same scope did not receive suspicious publishes on 2026-08-28. The maintainer's own npm credentials were not stolen - the takeover was via the CI workflow, not via account compromise

What to do

  1. 1Grep every lockfile (package-lock.json, pnpm-lock.yaml, yarn.lock, bun.lock) and node_modules/@7nohe/openapi-react-query-codegen/package.json for exact versions 0.5.4, 0.5.5, 1.6.3, 1.6.4, 2.2.1, 2.2.2, 3.0.3, 3.0.4, 0.0.0-365d4eb738d3146583431948d3ba6e27a32556be, 0.0.0-ec7876d6c917dad516ba69bbfafc948b834bf0ab
  2. 2Pin @7nohe/openapi-react-query-codegen to a version PUBLISHED BEFORE 2026-08-28 that you have independently verified against the upstream Git tag (a downgrade to 3.0.2 / 2.2.0 / 1.6.2 / 0.5.3 depending on your release train). Rebuild lockfiles after the pin, and re-run pnpm install --frozen-lockfile / npm ci to guarantee the compromised tarball is evicted from every workspace
  3. 3If any developer or CI runner installed one of the 10 flagged versions: treat that machine as fully compromised. Rotate every credential reachable from that machine's environment - starting with the developer's npm auth token (npm token revoke <hash> for every token the user owns), then GitHub PATs / fine-grained tokens / SSH keys under the same user, then cloud provider CLI credentials (aws configure --profile <p>, gcloud auth login, az login), then any secret exported into the runner's environment for the affected job
  4. 4Audit every other npm package the affected developer publishes for a trojanised republish. The worm attempts to auto-publish to every package the compromised token can reach. npm view <pkg> versions --json and cross-check the latest version's publish time against the developer's intended release cadence; any version published in the same window as the compromise but not matching an expected release is suspect
  5. 5For maintainers of any repository that uses on: issue_comment to trigger a publish, deploy, or write-scope job: add an author-association gate immediately. Minimum viable check: if: contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association). Even better: require the comment on a PR the maintainer authored, not on any PR. pull_request_target and issue_comment are the two most-abused triggers for supply-chain compromises via fork PRs - both need author gates AND minimal permissions: blocks (contents: read at the top of the workflow, elevated only in the specific step that needs it)
  6. 6For maintainers using npm trusted publishing (OIDC id-token: write): the same author-association gate applies, and the id-token: write permission should never be granted at the workflow level - only at the specific step that runs npm publish. A fork checkout that runs pnpm install before the publish step must not have any token-minting scope in its environment
  7. 7Watch for the "Trinitite" IOCs: a new GitHub repository under any of your developers' accounts named <Touhou-name>-<Touhou-name>-<number> with description exactly Trinitite: Sponsored by Preview 2 Effects and a results/doubletrinnys-*.json file is a live indicator of a compromised token in that user's environment. Delete the repo and rotate the token that could have created it
  8. 8Anyone who previously mirrored @7nohe/openapi-react-query-codegen to an internal registry (Nexus, Artifactory, Verdaccio) must audit that mirror: internal caches routinely keep serving the poisoned tarballs after the npm public takedown. Force a re-sync from upstream once you have confirmed the malicious versions are no longer served publicly

References

npm-2026-08-28-trinitite-openapi-react-query-codegen