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

ItemDetails
Package@7nohe/openapi-react-query-codegen
Attack dateAugust 28, 2026
Malicious releases10
Latest known malicious release3.0.4
Last known-good release3.0.2
Other known-good releases0.5.3, 1.6.2, 2.2.0
Primary loader3FWCvzduYZg.js
Approximate loader size5.6 MB
Installation triggersbinding.gyp, preinstall, and specialized prerelease paths
Secondary runtimeBun 1.4.0
Primary platformsLinux and macOS
Credential targetsGitHub, npm, PyPI, RubyGems, JFrog, AWS, Azure, Google Cloud, Vault and CI/CD
PersistenceLaunchAgent, systemd user service, developer-tool configuration
Propagationnpm, JFrog, RubyGems, PyPI and SSH
Command channelGitHub commit-search mechanism
ProvenanceValid npm/GitHub Actions provenance
AttributionBehavioral 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

The corresponding last known-good versions are:

0.5.x -> 0.5.3
1.6.x -> 1.6.2
2.2.x -> 2.2.0
3.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:

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
|
v
Pull request against legitimate repository
|
v
Comment-triggered GitHub Actions workflow
|
v
"npm publish" comment
|
v
Workflow checks out pull-request/fork code
|
v
pnpm install / build
|
v
Malicious installation trigger
|
v
3FWCvzduYZg.js
|
+----------------------------+
| |
v v
Credential discovery Secondary loaders
| |
v v
Credential 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
|
v
node-gyp processing
|
v
binding.gyp
|
v
Python expression
|
v
os.system()
|
v
node 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
|
v
node 3FWCvzduYZg.js
Wave 2:
binding.gyp
+
preinstall
|
v
node 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
|
v
malware starts
|
v
spawn detached child
|
v
suppress stdin/stdout/stderr
|
v
parent exits
|
v
payload 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:

  1. Files
  2. Processes and memory
  3. 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 tokens
npm tokens
RubyGems tokens
PyPI tokens
JFrog credentials
JWTs
connection strings
cloud 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/config
CI configuration
cloud configuration
shell history
IDE configuration
package-manager configuration
application 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 CLI
npm
cloud CLI
CI agent
IDE
development server
AI 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_TOKEN
ACTIONS_ID_TOKEN_REQUEST_TOKEN
ACTIONS_ID_TOKEN_REQUEST_URL
CI_JOB_TOKEN
GITLAB_TOKEN
NPM_TOKEN
ANTHROPIC_API_KEY
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
AWS_SESSION_TOKEN
AWS_WEB_IDENTITY_TOKEN_FILE
AZURE_CLIENT_ID
AZURE_CLIENT_SECRET
AZURE_FEDERATED_TOKEN_FILE
AZURE_TENANT_ID
GOOGLE_APPLICATION_CREDENTIALS
GOOGLE_CLOUD_PROJECT
VAULT_TOKEN
VAULT_ADDR
VERCEL
BUILD_NUMBER

It also contains logic for cloud metadata and identity services.

Observed endpoints include:

http://169.254.169.254/latest/api/token
http://169.254.170.2
http://metadata.google.internal
https://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
https://vault/
https://api.github.com
https://graph.microsoft.com/v1.0/servicePrincipals
https://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:

repo
public_repo

For npm, the malware checks token functionality and package-write capability.

It similarly validates:

PyPI
RubyGems
JFrog

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
|
v
JSON serialization
|
v
gzip compression
|
v
random AES-256 key
|
v
AES-256-GCM encryption
|
v
RSA-OAEP encryption of AES key
|
v
Base64 envelope

The relevant cryptographic components include:

AES-256-GCM
RSA-OAEP
SHA-256
32-byte random AES key
12-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
|
v
npm token stolen
|
v
Writable packages enumerated
|
v
Package downloaded
|
v
Malicious payload injected
|
v
Poisoned package published
|
v
Other 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 os
os.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:

  1. enumerates repositories;
  2. identifies repositories where the token can push;
  3. examines workflow files;
  4. identifies workflows capable of deployment;
  5. modifies repository Git objects;
  6. creates commits;
  7. 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.json
config.json
mcp.json
.aider.conf.yml
.cursorrules
.windsurfrules
.cursor/rules/
.github/copilot-instructions.md

It also looks for paths associated with tools such as:

Claude
Codex
Gemini
Copilot
Kiro
OpenCode
Cline
Aider
Tabby
Amazon Q
Cody
Bolt
Continue

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=no
ConnectTimeout=10
PasswordAuthentication=no
BatchMode=yes

It then uses scp to copy supporting payload files and attempts remote execution.

This creates a potential lateral-movement path:

Compromised workstation
|
v
SSH configuration
|
v
Known hosts
|
v
Reachable developer/server host
|
v
Payload copied
|
v
Remote 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:

RunAtLoad
KeepAlive

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-365d4eb738d3146583431948d3ba6e27a32556be
0.0.0-ec7876d6c917dad516ba69bbfafc948b834bf0ab
0.5.4
0.5.5
1.6.3
1.6.4
2.2.1
2.2.2
3.0.3
3.0.4

Known-good versions

0.5.3
1.6.2
2.2.0
3.0.2

These version indicators are among the highest-value checks for affected environments.

Threat-actor infrastructure

github[.]com/p00paboot
github[.]com/p00paboot/openapi-react-query-codegen

Associated malicious commit:

365d4eb738d3146583431948d3ba6e27a32556be

Malicious filenames

3FWCvzduYZg.js
binding.gyp
ai_init.js
ai_setup.sh
is_it_this_simple.js
nu.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

b49afb7dba04cd99b357ce7c652c823a3707f28e130bd5c6645851a7adc030d6
59370c67b54a0ccaedd265e2356f04540b2fba1e1845300ef6de4d5437d99380

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.js
WORKFLOW_ID=release.yml
REPO_ID_SUFFIX=7nohe/openapi-react-query-codegen
TARGET_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/user
https://api.github.com/search/commits
https://registry.npmjs.org/
https://registry.npmjs.org/-/npm/v1/tokens
https://registry.npmjs.org/-/whoami
https://upload.pypi.org/
https://pypi.org/
https://rubygems.org/
https://rubygems.org/api/v1/api_key.json
http://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 information
shell history
CI logs
GitHub audit records
npm logs
package-manager logs
LaunchAgent files
systemd user units
temporary files
developer-tool configuration
SSH 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 tokens
npm tokens
PyPI tokens
RubyGems credentials
JFrog credentials
AWS credentials
Azure credentials
Google Cloud credentials
Vault tokens
CI/CD secrets
SSH credentials
AI/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.3
1.6.2
2.2.0
3.0.2

For example:

npm install -D @7nohe/[email protected]

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 sysvinit
systemctl --user list-unit-files | grep -i sysvinit

Check known persistence locations:

find ~/.config -iname '*sysvinit*' -o -iname 'index.js' 2>/dev/null
find ~/.local/bin -iname '*sysvinit*' 2>/dev/null
find /tmp -maxdepth 3 -iname 'pcfg' -o -iname '.sshu-*' 2>/dev/null
find /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:

b49afb7dba04cd99b357ce7c652c823a3707f28e130bd5c6645851a7adc030d6
59370c67b54a0ccaedd265e2356f04540b2fba1e1845300ef6de4d5437d99380

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 creation
unexpected workflow changes
unexpected deployment creation
unexpected 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 versions
PyPI uploads
RubyGems releases
JFrog artifacts
package ownership changes
unexpected 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/config
cat ~/.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:

python
bash
sh
curl
scp
ssh
bun

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 repositories
workflow modifications
deployment creation
unexpected artifacts
unexpected Git commits
package 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 branches
required reviews
signed commits
immutable release commits
environment approvals
minimal OIDC permissions
isolated release jobs
reproducible builds
artifact verification
release provenance verification
manual 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 delivery
installation-time execution
Bun-based execution
credential harvesting
GitHub Actions targeting
package poisoning
GitHub infrastructure abuse
CI/CD compromise
cloud 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-365d4eb738d3146583431948d3ba6e27a32556be
0.0.0-ec7876d6c917dad516ba69bbfafc948b834bf0ab
0.5.4
0.5.5
1.6.3
1.6.4
2.2.1
2.2.2
3.0.3
3.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.3
BAD: 0.5.4, 0.5.5
GOOD: 1.6.2
BAD: 1.6.3, 1.6.4
GOOD: 2.2.0
BAD: 2.2.1, 2.2.2
GOOD: 3.0.2
BAD: 3.0.3, 3.0.4

The two malicious 0.0.0-* prereleases should also be considered compromised.

Do not use:

3.0.3
3.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