A widely used OpenAPI and React Query code-generation package was compromised in an ongoing npm supply-chain attack, with researchers identifying 10 malicious releases published on August 28, 2026.
The compromised package, @7nohe/openapi-react-query-codegen, is designed to generate TypeScript clients, React Query hooks, query options, and related API integration code from OpenAPI specifications. Its current legitimate release line is used by a substantial number of JavaScript and TypeScript projects, making the compromise particularly significant for development environments and CI/CD pipelines.
The attack is technically notable for a reason that goes beyond the malicious package itself: the attacker abused a GitHub Actions publishing workflow to publish attacker-controlled code through the project’s legitimate trusted-publishing identity.
As a result, the malicious npm releases carried valid npm provenance attestations. In other words, the package could appear to have been built and published through the legitimate project’s expected GitHub Actions workflow even though the source being published had been controlled by an attacker.
That distinction is critical. Software provenance can establish where and how an artifact was produced, but it cannot by itself establish that the source checked out by a trusted build workflow was trustworthy.
Researchers found that the malicious package executes code during installation and contains functionality capable of harvesting GitHub, npm, PyPI, RubyGems, JFrog, cloud, CI/CD, and developer-tool credentials. The recovered payload also contains mechanisms for package poisoning, GitHub Actions workflow modification, persistence in developer tooling, signed command retrieval, and SSH-based propagation.
The incident is therefore best understood as a developer-environment and software-supply-chain compromise, rather than an isolated malicious dependency.
OpenAPI React Query Codegen npm compromise at a glance
| Item | Details |
|---|---|
| Package | @7nohe/openapi-react-query-codegen |
| Attack date | August 28, 2026 |
| Malicious releases | 10 |
| Latest known malicious release | 3.0.4 |
| Last known-good release | 3.0.2 |
| Other known-good releases | 0.5.3, 1.6.2, 2.2.0 |
| Primary loader | 3FWCvzduYZg.js |
| Approximate loader size | 5.6 MB |
| Installation triggers | binding.gyp, preinstall, and specialized prerelease paths |
| Secondary runtime | Bun 1.4.0 |
| Primary platforms | Linux and macOS |
| Credential targets | GitHub, npm, PyPI, RubyGems, JFrog, AWS, Azure, Google Cloud, Vault and CI/CD |
| Persistence | LaunchAgent, systemd user service, developer-tool configuration |
| Propagation | npm, JFrog, RubyGems, PyPI and SSH |
| Command channel | GitHub commit-search mechanism |
| Provenance | Valid npm/GitHub Actions provenance |
| Attribution | Behavioral overlap with Mini Shai-Hulud; behavior alone does not prove operator attribution |
The affected package’s legitimate purpose is to generate React Query/TanStack Query integrations from OpenAPI schemas. The npm project describes functionality including generated query options, hooks, SSR helpers, hierarchical query keys and TypeScript clients.
Which versions of @7nohe/openapi-react-query-codegen are compromised?
Researchers identified the following 10 malicious versions:
@7nohe/openapi-react-query-codegen@0.0.0-365d4eb738d3146583431948d3ba6e27a32556be@7nohe/openapi-react-query-codegen@0.0.0-ec7876d6c917dad516ba69bbfafc948b834bf0ab@7nohe/[email protected]@7nohe/[email protected]@7nohe/[email protected]@7nohe/[email protected]@7nohe/[email protected]@7nohe/[email protected]@7nohe/[email protected]@7nohe/[email protected]
The corresponding last known-good versions are:
0.5.x -> 0.5.31.6.x -> 1.6.22.2.x -> 2.2.03.x -> 3.0.2
These version boundaries are particularly important because the attacker did not simply publish a new malicious major version. Instead, malicious releases were inserted into multiple maintained release lines, increasing the probability that existing applications would receive a compromised version through normal dependency resolution.
The npm package’s legitimate project is an OpenAPI-to-TanStack Query code generator, with the current clean release line documenting support for Node.js 22.18+, TanStack Query 5.x and TypeScript 5.x/6.x.
Why this npm compromise is unusually dangerous
An ordinary malicious dependency may attempt to steal environment variables or download another payload.
This campaign goes considerably further.
The recovered malware contains functionality for:
- credential discovery from source trees and dotfiles;
- process-memory inspection;
- CI/CD environment harvesting;
- cloud metadata access;
- GitHub token validation;
- npm token validation;
- PyPI credential validation;
- RubyGems credential validation;
- JFrog credential validation;
- package poisoning;
- GitHub Actions workflow modification;
- CI/CD secret extraction;
- developer-tool persistence;
- AI coding assistant configuration modification;
- remote command retrieval;
- SSH-based propagation;
- and additional self-propagation mechanisms.
The supplied research emphasizes that the package was analyzed through archive extraction, deobfuscation and source inspection rather than executing the malicious package or its secondary components. That distinction matters when interpreting the findings.
The broader Mini Shai-Hulud campaign has also been documented by Microsoft as a supply-chain operation targeting developer environments, CI/CD pipelines and cloud-connected workloads. Microsoft previously reported a large resurgence involving hundreds of malicious npm and PyPI versions.
The attack chain
At a high level, the compromise follows this sequence:
Attacker-controlled GitHub fork | vPull request against legitimate repository | vComment-triggered GitHub Actions workflow | v"npm publish" comment | vWorkflow checks out pull-request/fork code | vpnpm install / build | vMalicious installation trigger | v3FWCvzduYZg.js | +----------------------------+ | | v vCredential discovery Secondary loaders | | v vCredential validation Bun / shell / Python | | +-------------+--------------+ | v Encrypted data collection | v GitHub infrastructure | +-------------+-------------+ | | | v v v npm RubyGems JFrog | v Optional PyPI typosquatting | v Further propagation
The important architectural point is that the initial compromise and subsequent payload execution are separate problems.
The attacker first gained a publishing path. The malicious package then became the delivery mechanism. Once installed, the malware attempted to convert the victim’s developer environment into another source of credentials, package permissions and CI/CD access.
That is what gives the attack worm-like characteristics.
How the attacker abused GitHub Actions
The most important part of the incident is the publishing workflow.
According to the technical analysis, the repository’s release workflow was triggered by an issue_comment event. The workflow accepted a comment containing:
npm publish
The relevant condition effectively permitted the workflow to run when the comment text matched that string:
if: ${{ github.event_name == 'push' || (github.event.issue.pull_request && github.event.comment.body == 'npm publish') }}
The critical weakness was not the comment itself.
The problem was that the workflow did not adequately establish who was allowed to invoke the publishing operation.
The workflow checked out the pull-request head from a fork and subsequently ran the package publishing process with an OIDC identity capable of trusted npm publishing.
The research found no effective check of the commenter’s repository association before granting access to the publishing job. Consequently, an attacker could use a forked repository to supply modified source code and then trigger the release workflow by posting the expected comment.
This is a classic CI/CD trust-boundary failure.
The workflow effectively treated:
"someone requested a release"
as equivalent to:
"an authorized maintainer approved this exact source tree for publication"
Those are not the same security assertion.
Why npm provenance did not stop the attack
One of the most important lessons from this incident concerns software supply-chain provenance.
All 10 malicious releases reportedly carried valid npm provenance attestations generated through GitHub Actions trusted publishing. The attestations referenced the expected workflow and legitimate repository context.
At first glance, that sounds contradictory.
How can malware have valid provenance?
Because provenance answers a narrower question:
Was this artifact produced through the claimed build and publishing mechanism?
It does not automatically answer:
Was the source code supplied to that mechanism trustworthy?
In this incident, the attacker abused the mechanism itself.
The malicious artifact could therefore have a valid cryptographic provenance chain while still containing attacker-controlled code.
This is a crucial distinction for modern software-security programs.
A provenance statement is not a magical malware certificate.
A useful conceptual model is:
Source integrity +Build integrity +Workflow integrity +Identity authorization +Artifact provenance =Meaningful supply-chain trust
If workflow authorization is broken, strong provenance can still describe an attacker-controlled build accurately.
The supplied research specifically notes that the malicious attestations referenced the legitimate release workflow and the clean 3.0.2 commit context, explaining why provenance verification alone did not expose the malicious contents.
How the malicious npm package executes
The main malicious payload is:
3FWCvzduYZg.js
The file is approximately 5.6 MB and is heavily obfuscated.
The package also contains a malicious:
binding.gyp
The use of binding.gyp is significant because the package did not need a legitimate native implementation to use it as an execution mechanism.
Instead, the malicious build configuration abuses Python object traversal to reach Python builtins and eventually invoke os.system().
The final command resolves to:
node 3FWCvzduYZg.js
A simplified representation of the execution chain is:
npm install | vnode-gyp processing | vbinding.gyp | vPython expression | vos.system() | vnode 3FWCvzduYZg.js
The actual malicious expression is Unicode-escaped and intentionally difficult to recognize during casual source inspection.
This is an important detection lesson: security review of npm packages cannot focus exclusively on package.json lifecycle scripts.
Native-build metadata such as binding.gyp can also become an execution surface.
Two waves of malicious releases
The compromised versions were published in two waves.
For the primary release lines, the first wave relied on the malicious binding.gyp mechanism.
The second wave retained the same mechanism while adding an explicit:
"preinstall": "node 3FWCvzduYZg.js"
That provided a second installation-time execution trigger.
The supplied research summarizes the distinction as follows:
Wave 1:binding.gyp | vnode 3FWCvzduYZg.jsWave 2:binding.gyp +preinstall | vnode 3FWCvzduYZg.js
The two 0.0.0-* prereleases followed separate execution paths involving is_it_this_simple.js, Bun and nu.js.
The 5.6 MB obfuscated JavaScript loader
The primary JavaScript file is unusually large for an installation loader.
Researchers measured it at approximately:
5,616,000 bytes
The file contains no normal line structure and begins with an XOR-decoding function using the JavaScript Function constructor.
The general structure resembles:
Function( decode(encodedData, key))();
The important defensive takeaway is not the exact decoder.
It is the combination of:
Large single-line JavaScript+XOR transformation+Function constructor+Embedded module table+Dynamic execution
That combination should be treated as a high-risk indicator inside an npm package that otherwise has no legitimate reason to ship such a loader.
Bun 1.4.0 becomes part of the execution chain
The recovered payload contains a platform-aware loader that attempts to obtain Bun version:
1.4.0
The malware selects the appropriate architecture-specific asset and retrieves it from the official Bun GitHub release infrastructure.
The relevant release path follows this general structure:
https://github.com/oven-sh/bun/releases/download/bun-v1.4.0/
The fact that the malware uses a legitimate upstream project for payload execution is another important detection challenge.
Security teams should distinguish between:
malicious infrastructure
and:
legitimate infrastructure abused by malicious code
Blocking GitHub or the Bun project wholesale would be both disruptive and ineffective.
The supplied research explicitly lists these legitimate service endpoints as infrastructure that should not simply be blanket-blocked.
Anti-analysis and detached execution
The malware attempts to separate itself from the original package installation process.
One mechanism uses:
__DOGINSIDEPC
as a relaunch guard.
If the environment variable is absent, the malware launches a detached child process, suppresses its standard streams and terminates the original parent process.
Conceptually:
npm lifecycle process | vmalware starts | vspawn detached child | vsuppress stdin/stdout/stderr | vparent exits | vpayload continues independently
This makes straightforward process monitoring less reliable because the process that launched the malware may disappear while the detached payload continues running.
Credential harvesting: the real objective
The malware is not primarily interested in the OpenAPI code-generation functionality.
Its real value is the credentials available in the environment where developers and CI systems install packages.
The recovered payload has three broad initial discovery providers:
async function G2() { let filesystem = new N8(); let processMemory = new E8(); let environment = new _8(); return [ await filesystem.execute(), await processMemory.execute(), await environment.execute() ];}
This gives the malware three complementary views of a victim:
- Files
- Processes and memory
- Environment and CI/CD configuration
The combination is considerably more powerful than simply scanning .env files.
Filesystem credential discovery
The filesystem scanner recursively walks the current working environment, including dotfiles, and searches for patterns associated with credentials.
Examples documented by researchers include patterns corresponding to:
GitHub tokensnpm tokensRubyGems tokensPyPI tokensJFrog credentialsJWTsconnection stringscloud credentials
Examples of token patterns recovered from the malware include:
/gh[op]_[A-Za-z0-9]{36,}/g/github_pat_[A-Za-z0-9_]{30,}/g/npm_[A-Za-z0-9]{36,}/g/rubygems_[A-Za-z0-9_-]{32,}/g
The scanner can inspect up to approximately 12,000 files during its recursive search.
This is particularly dangerous in developer environments because credentials may exist in:
.env.env.local.npmrc.git/configCI configurationcloud configurationshell historyIDE configurationpackage-manager configurationapplication caches
The presence of a credential on disk should therefore be treated as equivalent to credential exposure if the host installed a malicious version.
Process-memory credential theft
The malware goes beyond filesystem scanning.
A recovered Python component reads Linux process memory through:
/proc/<pid>/maps/proc/<pid>/mem
The mechanism identifies readable memory regions and extracts their contents for subsequent credential pattern matching.
This is significant because a secret does not need to exist in a configuration file to be exposed.
For example, a developer may have a valid token loaded only into:
GitHub CLInpmcloud CLICI agentIDEdevelopment serverAI coding assistant
A memory-aware credential stealer can potentially recover such secrets even when filesystem searches produce nothing.
CI/CD and cloud credential discovery
The environment collector enumerates a large collection of variables, including:
GITHUB_TOKENACTIONS_ID_TOKEN_REQUEST_TOKENACTIONS_ID_TOKEN_REQUEST_URLCI_JOB_TOKENGITLAB_TOKENNPM_TOKENANTHROPIC_API_KEYAWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYAWS_SESSION_TOKENAWS_WEB_IDENTITY_TOKEN_FILEAZURE_CLIENT_IDAZURE_CLIENT_SECRETAZURE_FEDERATED_TOKEN_FILEAZURE_TENANT_IDGOOGLE_APPLICATION_CREDENTIALSGOOGLE_CLOUD_PROJECTVAULT_TOKENVAULT_ADDRVERCELBUILD_NUMBER
It also contains logic for cloud metadata and identity services.
Observed endpoints include:
http://169.254.169.254/latest/api/tokenhttp://169.254.170.2http://metadata.google.internalhttps://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/tokenhttps://vault/https://api.github.comhttps://graph.microsoft.com/v1.0/servicePrincipalshttps://management.azure.com/https://login.microsoftonline.com/
These endpoints should be treated as IOC/context indicators, not domains that should automatically be blocked. Many are legitimate infrastructure services used by normal applications and cloud workloads.
The malware validates stolen credentials before using them
Another technically important characteristic is that the payload does not simply dump every possible string and send it away.
It attempts to determine whether discovered credentials are valid and what permissions they possess.
For GitHub tokens, it queries GitHub APIs and examines scopes.
For example, repository-related actions are enabled when a token has relevant repository permissions such as:
repopublic_repo
For npm, the malware checks token functionality and package-write capability.
It similarly validates:
PyPIRubyGemsJFrog
credentials before enabling corresponding functionality.
This capability selection is important from a defender’s perspective because an exposed credential does not necessarily result in the same level of compromise.
A read-only GitHub token and a package-publishing token represent very different levels of risk.
Encrypted data collection and exfiltration
The payload batches collected information before transmission.
The documented default flush threshold is:
0x19000
or:
102,400 bytes
The recovered encryption process uses a hybrid cryptographic construction:
Collected JSON | vJSON serialization | vgzip compression | vrandom AES-256 key | vAES-256-GCM encryption | vRSA-OAEP encryption of AES key | vBase64 envelope
The relevant cryptographic components include:
AES-256-GCMRSA-OAEPSHA-25632-byte random AES key12-byte GCM nonce
The payload then uses GitHub infrastructure as an operational storage and transport mechanism.
Researchers found code that creates a public GitHub repository and commits the encrypted data to it. The repository description recovered from the malware is:
Trinitite: Sponsored by Preview 2 Effects
The repository names are generated dynamically and follow a Touhou-themed naming pattern.
This technique is particularly effective because the attacker can hide encrypted data inside a legitimate developer platform instead of relying exclusively on an obviously malicious command-and-control domain.
npm package poisoning and supply-chain propagation
The most dangerous capability may be what happens after the attacker obtains a package-publishing credential.
The malware can enumerate packages that a stolen credential can modify.
It then downloads package tarballs, modifies them, and publishes poisoned versions.
This turns a single infected developer workstation into a potential supply-chain launch point.
The conceptual propagation chain is:
Compromised developer | vnpm token stolen | vWritable packages enumerated | vPackage downloaded | vMalicious payload injected | vPoisoned package published | vOther developers install it
The JFrog path provides a similar mechanism for packages stored in writable repositories.
The recovered processor uses an explicit:
[jfrognpm]
tag and can add a Bun dependency while modifying the package.
RubyGems propagation
The campaign is not limited to JavaScript.
The malware contains RubyGems support capable of enumerating gems owned by a compromised account, downloading them, modifying them and submitting modified packages through the RubyGems API.
The relevant capability is enabled after validating the RubyGems credential and checking for the:
index_rubygems
permission.
This cross-ecosystem capability increases the blast radius considerably.
A JavaScript developer’s stolen credential could potentially be used to attack a package ecosystem that is not directly related to the original npm dependency.
PyPI typosquatting capability
The malware also contains a gated PyPI propagation mode.
The mode requires:
TYPO_MODE=1
and populated target package information.
It can generate package-name variants using suffixes such as:
-mcp-mpc
and can inject a Python payload into generated packages.
The supplied research recovered a minimal fallback payload containing:
import osos.system('id')
The presence of this code demonstrates that the PyPI mechanism is an operator-controlled propagation capability rather than functionality required by the original package.
GitHub Actions workflow poisoning
The malware can also weaponize GitHub repositories after obtaining a sufficiently privileged GitHub token.
The recovered GitHub module:
- enumerates repositories;
- identifies repositories where the token can push;
- examines workflow files;
- identifies workflows capable of deployment;
- modifies repository Git objects;
- creates commits;
- injects malicious workflow code.
One recovered workflow is named:
ClaudeCode Review
It triggers on:
deployment:
and exposes repository secrets through the workflow environment.
The recovered payload writes the serialized secret environment into:
res.txt
and uploads that file as a GitHub Actions artifact named:
reviewed
The malware can also create a commit using:
chore: update dependencies
followed by another commit containing:
[skip ci]
This makes the compromise resemble routine repository maintenance.
For incident responders, these strings are therefore useful hunting indicators.
AI coding assistant and developer-tool persistence
One of the most contemporary aspects of the malware is its focus on AI-assisted development environments.
The payload searches for developer-tool configuration files including:
settings.jsonconfig.jsonmcp.json.aider.conf.yml.cursorrules.windsurfrules.cursor/rules/.github/copilot-instructions.md
It also looks for paths associated with tools such as:
ClaudeCodexGeminiCopilotKiroOpenCodeClineAiderTabbyAmazon QCodyBoltContinue
When it finds writable configuration files, the payload can create:
.config/index.js
and inject a command intended to run during a developer-tool session.
The recovered code describes the mechanism as a background task intended to execute silently when a session begins.
This creates an important distinction between:
package compromise
and:
persistent developer-environment compromise
Even after node_modules is deleted, malicious configuration may remain.
Signed GitHub commit messages as a covert command channel
The payload contains another unusual mechanism: a command channel built around GitHub commit messages.
The recovered code searches GitHub commits for a specific query:
firedalazer
It then looks for messages matching a structure similar to:
n1ggatr1n <base64-url>.<base64-signature>
The signature is verified using an embedded public key.
If valid, the malware downloads the referenced content, writes it to a temporary Python file and executes it with:
python3
A local hash is stored under:
/var/tmp/.shit
to avoid executing the same command repeatedly.
From a defender’s perspective, this is another example of a modern malware trend: abusing legitimate public platforms as command infrastructure.
SSH-based propagation
The recovered malware also examines local SSH configuration and known-host information to identify potential propagation targets.
The recovered implementation invokes SSH with options including:
StrictHostKeyChecking=noConnectTimeout=10PasswordAuthentication=noBatchMode=yes
It then uses scp to copy supporting payload files and attempts remote execution.
This creates a potential lateral-movement path:
Compromised workstation | vSSH configuration | vKnown hosts | vReachable developer/server host | vPayload copied | vRemote execution
Organizations should therefore examine outbound SSH activity from affected developer machines, particularly activity that occurs shortly after installation of one of the malicious package versions.
Persistence on macOS and Linux
The malware includes operating-system-specific persistence mechanisms.
macOS
The payload creates:
~/Library/LaunchAgents/com.user.sysvinit-detect-fash.plist
and uses LaunchAgent behavior including:
RunAtLoadKeepAlive
Linux
The payload creates a user-level systemd service:
~/.config/systemd/user/sysvinit-detect-fash.service
It also uses:
~/.config/sysvinit-detect-fash/
and related files.
Other persistence and staging paths include:
~/.local/bin/sysvinit-detect-fash.sh~/.config/sysvinit-detect-fash/fox~/.config/sysvinit-detect-fash/fash-detected~/.config/sysvinit-detect-fash/runit/var/tmp/.shit/tmp/.sshu-<random>/tmp/pcfg/.config/index.js.github/_index.js
These indicators are directly supported by the supplied technical analysis.
GitHub token monitoring
A particularly concerning persistence mechanism monitors a GitHub token.
The malware installs a monitor that periodically queries:
https://api.github.com/user
using a stored bearer token.
Researchers found that the monitor checks the resulting HTTP status and can execute a stored handler when GitHub returns a 4xx response.
The monitoring infrastructure can remain active for up to 72 hours according to the recovered code, despite associated log text describing a shorter lifetime.
This is why responders should be careful about immediately revoking credentials on an infected host without first understanding the installed persistence.
The supplied research specifically recommends containment before credential revocation because the token-monitoring component can react to token status changes.
Indicators of compromise
Malicious npm package
@7nohe/openapi-react-query-codegen
Compromised versions
0.0.0-365d4eb738d3146583431948d3ba6e27a32556be0.0.0-ec7876d6c917dad516ba69bbfafc948b834bf0ab0.5.40.5.51.6.31.6.42.2.12.2.23.0.33.0.4
Known-good versions
0.5.31.6.22.2.03.0.2
These version indicators are among the highest-value checks for affected environments.
Threat-actor infrastructure
github[.]com/p00pabootgithub[.]com/p00paboot/openapi-react-query-codegen
Associated malicious commit:
365d4eb738d3146583431948d3ba6e27a32556be
Malicious filenames
3FWCvzduYZg.jsbinding.gypai_init.jsai_setup.shis_it_this_simple.jsnu.js~/.local/bin/sysvinit-detect-fash.sh~/.config/sysvinit-detect-fash/fox~/.config/sysvinit-detect-fash/fash-detected~/.config/sysvinit-detect-fash/runit~/Library/LaunchAgents/com.user.sysvinit-detect-fash.plist~/.config/systemd/user/sysvinit-detect-fash.service/var/tmp/.shit/tmp/.sshu-<random>/tmp/pcfg/.config/index.js.github/_index.js
File hashes
3FWCvzduYZg.js
b49afb7dba04cd99b357ce7c652c823a3707f28e130bd5c6645851a7adc030d659370c67b54a0ccaedd265e2356f04540b2fba1e1845300ef6de4d5437d99380
binding.gyp
d3246926b20a8d021ed7de0ac8e9eee1dda986088f84ba18f31cb2042a121f5d
Security teams should preferably use the package version, exact file hashes and behavioral indicators together. Hash-only detection is fragile because subsequent propagation can introduce modified or repacked payloads.
Execution and environment indicators
preinstall: node 3FWCvzduYZg.jsWORKFLOW_ID=release.ymlREPO_ID_SUFFIX=7nohe/openapi-react-query-codegenTARGET_PACKAGES=@7nohe/openapi-react-query-codegen
Legitimate infrastructure that should not automatically be blocked
Some infrastructure observed during the investigation belongs to legitimate services:
https://github.com/https://github.com/oven-sh/bun/releases/download/bun-v1.4.0/https://api.github.com/https://api.github.com/userhttps://api.github.com/search/commitshttps://registry.npmjs.org/https://registry.npmjs.org/-/npm/v1/tokenshttps://registry.npmjs.org/-/whoamihttps://upload.pypi.org/https://pypi.org/https://rubygems.org/https://rubygems.org/api/v1/api_key.jsonhttp://169.254.169.254/http://169.254.170.2/http://metadata.google.internal/https://graph.microsoft.com/https://management.azure.com/https://login.microsoftonline.com/
These should be treated as behavioral indicators and investigation pivots, not simple blocklist entries.
How to check whether a project installed a compromised version
Start with the dependency tree.
For npm:
npm ls @7nohe/openapi-react-query-codegen
For pnpm:
pnpm why @7nohe/openapi-react-query-codegen
For Yarn:
yarn why @7nohe/openapi-react-query-codegen
Search lockfiles:
grep -R "@7nohe/openapi-react-query-codegen" \ package.json package-lock.json pnpm-lock.yaml yarn.lock 2>/dev/null
A compromised version anywhere in a dependency tree should be treated seriously, including a transitive dependency.
The supplied research specifically recommends checking lockfiles and SBOMs and not relying solely on npm signature or provenance validation.
Safe remediation procedure
If an affected version was installed, the safest response is to treat the host as potentially compromised.
1. Isolate the host
Disconnect the affected developer machine or CI runner from the network.
Do not assume that removing the npm package immediately eliminates the threat.
The recovered malware includes persistence mechanisms independent of the original node_modules directory.
2. Preserve evidence
Before deleting suspicious files, preserve:
process informationshell historyCI logsGitHub audit recordsnpm logspackage-manager logsLaunchAgent filessystemd user unitstemporary filesdeveloper-tool configurationSSH logs
This is particularly important if the host had access to production credentials or package-publishing permissions.
3. Rebuild where possible
For developer workstations and CI runners, rebuilding from a known-clean image is preferable to assuming that all persistence has been found.
The reason is straightforward: the malware can modify locations outside the original npm installation.
4. Rotate credentials from a clean system
After containment, rotate credentials that could have been exposed, including:
GitHub tokensnpm tokensPyPI tokensRubyGems credentialsJFrog credentialsAWS credentialsAzure credentialsGoogle Cloud credentialsVault tokensCI/CD secretsSSH credentialsAI/developer-tool credentials
Do this from a trusted management plane rather than the potentially infected host.
5. Reinstall from a known-good version
Use one of the known-good versions:
0.5.31.6.22.2.03.0.2
For example:
Then regenerate the lockfile from a clean environment.
Do not simply reinstall without controlling the exact version.
Search for persistence
On macOS:
ls -la ~/Library/LaunchAgents/grep -R "sysvinit-detect-fash" ~/Library/LaunchAgents 2>/dev/null
On Linux:
systemctl --user list-units --all | grep -i sysvinitsystemctl --user list-unit-files | grep -i sysvinit
Check known persistence locations:
find ~/.config -iname '*sysvinit*' -o -iname 'index.js' 2>/dev/nullfind ~/.local/bin -iname '*sysvinit*' 2>/dev/nullfind /tmp -maxdepth 3 -iname 'pcfg' -o -iname '.sshu-*' 2>/dev/nullfind /var/tmp -maxdepth 2 -name '.shit' 2>/dev/null
Also inspect developer-tool configuration:
find "$HOME" \ \( -name 'mcp.json' \ -o -name '.cursorrules' \ -o -name '.windsurfrules' \ -o -name '.aider.conf.yml' \ -o -path '*/.cursor/rules/*' \ -o -path '*/.github/copilot-instructions.md' \) \ -print 2>/dev/null
Hunt for the malicious loader
On Linux or macOS:
find . \ -type f \ \( -name '3FWCvzduYZg.js' \ -o -name 'binding.gyp' \ -o -name 'ai_init.js' \ -o -name 'ai_setup.sh' \ -o -name 'is_it_this_simple.js' \ -o -name 'nu.js' \) \ -print
For a known loader:
sha256sum 3FWCvzduYZg.js
Compare the resulting hash against:
b49afb7dba04cd99b357ce7c652c823a3707f28e130bd5c6645851a7adc030d659370c67b54a0ccaedd265e2356f04540b2fba1e1845300ef6de4d5437d99380
For binding.gyp:
sha256sum binding.gyp
Expected malicious hash:
d3246926b20a8d021ed7de0ac8e9eee1dda986088f84ba18f31cb2042a121f5d
Hunt GitHub activity after an infection
Security teams should review GitHub audit logs for:
unexpected repository creationunexpected workflow changesunexpected deployment creationunexpected package-related commits"chore: update dependencies"[skip ci]
workflow files modified unexpectedly artifacts named “reviewed”
The presence of one of these indicators does not prove compromise by itself. The strongest signal is correlation with:
affected npm version+installation on a privileged host+credential exposure+unexpected GitHub activity
Check package registries for secondary compromise
If the affected environment had package-publishing permissions, investigate publishing activity after the installation time.
Review:
npm package versionsPyPI uploadsRubyGems releasesJFrog artifactspackage ownership changesunexpected maintainer activity
Pay particular attention to versions published shortly after the affected package was installed.
The reason is that the malware can use stolen package credentials to propagate beyond the original package.
Check SSH activity
Because the payload contains SSH propagation functionality, review:
grep -iE 'ssh|scp' ~/.bash_history ~/.zsh_history 2>/dev/null
and system authentication logs where available.
Also inspect:
cat ~/.ssh/configcat ~/.ssh/known_hosts
for unexpected destinations.
Do not treat the presence of a host in known_hosts as evidence of compromise by itself. The useful signal is an unexpected outbound connection from an affected host after package installation.
Detection engineering opportunities
The incident provides several useful behavioral detections.
Suspicious npm installation behavior
Look for:
npm install | +-- node-gyp | +-- binding.gyp | +-- Python execution | +-- os.system | +-- node 3FWCvzduYZg.js
A package invoking an unexpected native build step despite having no legitimate native component deserves investigation.
Suspicious child process behavior
Look for Node.js or npm spawning:
pythonbashshcurlscpsshbun
during dependency installation.
The context matters. These processes are not inherently malicious, but the combination of installation-time execution and credential access is high risk.
Suspicious GitHub behavior
Monitor for:
new public repositoriesworkflow modificationsdeployment creationunexpected artifactsunexpected Git commitspackage publishing activity
from developer accounts or CI identities that normally do not perform those operations.
Why this incident matters for trusted publishing
The most important lesson is not simply:
“Don’t install malicious npm packages.”
Modern software development cannot operate without third-party dependencies.
The more useful lesson is:
Trusted build infrastructure must not automatically trust untrusted source inputs.
A secure release workflow should establish all of the following independently:
Who requested publication? +Which repository is being built? +Which commit is being built? +Was that commit approved? +Was the source reviewed? +Was the workflow triggered by an authorized identity? +Are privileged OIDC permissions necessary? +Does the artifact match the expected source?
The vulnerable workflow effectively collapsed several of these trust decisions into one comment-triggered action.
How maintainers can prevent similar GitHub Actions abuse
A workflow that publishes packages should not grant publishing privileges merely because a comment contains a magic phrase.
At minimum, maintainers should validate the actor and pull-request context before invoking privileged jobs.
For example, a defensive condition can incorporate repository association:
if: > github.event_name == 'push' || ( github.event.issue.pull_request && github.event.comment.body == 'npm publish' && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) )
Even that should not be considered a complete supply-chain security control.
For high-value releases, maintainers should additionally consider:
protected branchesrequired reviewssigned commitsimmutable release commitsenvironment approvalsminimal OIDC permissionsisolated release jobsreproducible buildsartifact verificationrelease provenance verificationmanual publication approval
The core rule is simple:
Never let an untrusted pull-request contributor directly control a job that possesses package-publishing credentials or OIDC publishing authority.
Provenance should be combined with source verification
The incident demonstrates why organizations should avoid treating provenance as a binary:
provenance = trusted
Instead:
provenance+source identity+commit integrity+workflow integrity+authorization+artifact comparison
should form the trust decision.
This is particularly important for organizations adopting SLSA-style provenance, Sigstore, OIDC trusted publishing and increasingly automated CI/CD pipelines.
Cryptographic signatures are powerful, but they cannot compensate for an authorization flaw inside the system generating the signature.
Is this definitely the same Shai-Hulud operator?
The safest answer is: the technical behavior strongly overlaps with Mini Shai-Hulud, but behavioral overlap alone does not prove operator attribution.
The recovered package shares several characteristics with publicly reported Mini Shai-Hulud activity:
npm supply-chain deliveryinstallation-time executionBun-based executioncredential harvestingGitHub Actions targetingpackage poisoningGitHub infrastructure abuseCI/CD compromisecloud credential theft
Microsoft has separately documented Mini Shai-Hulud activity involving compromised npm packages, credential theft, Bun and CI/CD targeting.
Endor Labs has also independently reported the August 28, 2026 compromise of @7nohe/openapi-react-query-codegen, describing the malware as capable of harvesting credentials and propagating through npm, RubyGems and PyPI.
SafeDep similarly reports that the attacker abused a GitHub Actions issue_comment workflow to obtain trusted npm publishing capability without directly stealing the package maintainer’s npm credentials.
However, attribution should remain conservative unless independent infrastructure, cryptographic, operational or intelligence evidence establishes the connection.
What developers should do right now
If you have used:
@7nohe/openapi-react-query-codegen
check the installed version immediately.
Run:
npm ls @7nohe/openapi-react-query-codegen
If the result is any of:
0.0.0-365d4eb738d3146583431948d3ba6e27a32556be0.0.0-ec7876d6c917dad516ba69bbfafc948b834bf0ab0.5.40.5.51.6.31.6.42.2.12.2.23.0.33.0.4
assume the environment may have been exposed.
Do not simply delete the package and continue working.
Instead:
1. Isolate the machine.2. Preserve evidence.3. Determine installation time.4. Identify credentials available to the host.5. Inspect persistence.6. Rebuild from a known-clean image where possible.7. Rotate exposed credentials from a clean system.8. Audit GitHub and package registries.9. Check for secondary package propagation.10. Reinstall only a known-good package version.
This response sequence is consistent with the containment and remediation guidance in the underlying research.
The four versions developers should remember
If teams only remember one thing from this incident, it should be the version boundary:
GOOD: 0.5.3BAD: 0.5.4, 0.5.5GOOD: 1.6.2BAD: 1.6.3, 1.6.4GOOD: 2.2.0BAD: 2.2.1, 2.2.2GOOD: 3.0.2BAD: 3.0.3, 3.0.4
The two malicious 0.0.0-* prereleases should also be considered compromised.
Do not use:
3.0.33.0.4
simply because they appear to be newer versions.
In this incident, newer did not mean safer.
Final assessment
The @7nohe/openapi-react-query-codegen compromise demonstrates how modern software supply-chain attacks are evolving.
The attacker did not need to compromise npm itself.
They did not need to break the cryptography behind trusted publishing.
They did not need to convince developers to run an obviously malicious executable.
Instead, they abused a weakness at the intersection of:
GitHub pull requests+GitHub Actions+OIDC trusted publishing+npm provenance+developer package installation+credential-rich development environments
The result was a malicious package that could execute during installation, harvest credentials from files and process memory, inspect cloud metadata, validate package-publishing privileges, alter GitHub Actions workflows, poison additional packages, establish persistence in developer tooling, communicate through GitHub infrastructure and attempt SSH-based propagation.
The most consequential lesson is therefore broader than this one npm package.
A trusted build system is only as trustworthy as the inputs it is authorized to build.
Provenance can prove that an artifact came through a legitimate workflow. It cannot make attacker-controlled source code legitimate.
For developers, the immediate priority is version verification and host containment.
For maintainers, the priority is reviewing every privileged GitHub Actions workflow that can be triggered from pull requests, comments or other attacker-influenced events.
For security teams, this incident is another reminder that the developer workstation, CI runner and package registry are no longer separate security boundaries. In a modern software supply chain, compromise of one can quickly become compromise of all three.
This post first appeared at - The CyberSec Guru