Back to Blog

Multiple redhat-cloud-services npm Packages compromised

Several packages in the @redhat-cloud-services npm scope were found to carry malicious payloads that fire via a preinstall hook on every npm install. The affected versions span multiple packages across the RedHat Cloud Services frontend ecosystem. The payload is a sophisticated multi-stage credential harvester that targets GitHub Actions secrets, AWS, GCP, Azure, Kubernetes, HashiCorp Vault, npm tokens, and CircleCI tokens
Rohan Prabhu
View LinkedIn

June 1, 2026

Share on X
Share on X
Share on LinkedIn
Share on Facebook
Follow our RSS feed
Table of Contents

On June 1, 2026, StepSecurity found that several packages in the @redhat-cloud-services npm scope were shipping malware that runs automatically on every npm install, before any application code executes. The payload is a multi-stage credential harvester that sweeps GitHub Actions secrets along with AWS, GCP, Azure, Kubernetes, HashiCorp Vault, npm, and CircleCI tokens, and it is purpose-built to evade detection, including an explicit attempt to bypass StepSecurity Harden-Runner.

StepSecurity analyzed @redhat-cloud-services/host-inventory-client@5.0.3 in full. Its index.js, executed at install time, is 4.2 MB, a file that should weigh a few kilobytes, with the real payload buried under three separate layers of obfuscation. The malware is also a self-propagating worm: using stolen npm tokens and npm's bypass_2fa parameter, it republishes backdoored versions of other packages on its own, even against accounts protected by two-factor authentication, so every infected machine can seed the next wave with no attacker involvement. All affected packages were published via GitHub Actions OIDC from the RedHatInsights/javascript-clients repository, indicating the upstream CI/CD pipeline itself was compromised. Analysis of the remaining packages is ongoing.

If you or your pipelines have installed any of the affected versions, assume that your CI/CD environment or developer machine is already compromised, and rotate exposed credentials immediately.

StepSecurity has filed disclosure issues in all three upstream repositories and is working with the maintainers to confirm the scope of the pipeline compromise and coordinate removal of the malicious releases:

Affected Packages

Package
Malicious Version
Weekly Downloads
@redhat-cloud-services/types
3.6.1
15,060
@redhat-cloud-services/frontend-components-utilities
7.4.1
14,166
@redhat-cloud-services/frontend-components
7.7.2
13,721
@redhat-cloud-services/rbac-client
9.0.3
13,551
@redhat-cloud-services/javascript-clients-shared
2.0.8
13,006
@redhat-cloud-services/frontend-components-config-utilities
4.11.2
9,393
@redhat-cloud-services/frontend-components-notifications
6.9.2
7,841
@redhat-cloud-services/tsc-transform-imports
1.2.2
6,675
@redhat-cloud-services/frontend-components-config
6.11.3
6,515
@redhat-cloud-services/eslint-config-redhat-cloud-services
3.2.1
5,214
@redhat-cloud-services/host-inventory-client
5.0.3
3,507
@redhat-cloud-services/rule-components
4.7.2
1,293
@redhat-cloud-services/frontend-components-remediations
4.9.2
1,170
@redhat-cloud-services/frontend-components-translations
4.4.1
935
@redhat-cloud-services/frontend-components-advisor-components
3.8.2
697
@redhat-cloud-services/entitlements-client
4.0.11
576
@redhat-cloud-services/chrome
2.3.1
523
@redhat-cloud-services/notifications-client
6.1.4
252
@redhat-cloud-services/compliance-client
4.0.3
228
@redhat-cloud-services/sources-client
3.0.10
223
@redhat-cloud-services/integrations-client
6.0.4
221
@redhat-cloud-services/frontend-components-testing
1.2.1
219
@redhat-cloud-services/remediations-client
4.0.4
219
@redhat-cloud-services/insights-client
4.0.4
203
@redhat-cloud-services/topological-inventory-client
3.0.10
185
@redhat-cloud-services/config-manager-client
5.0.4
180
@redhat-cloud-services/hcc-pf-mcp
0.6.1
155
@redhat-cloud-services/quickstarts-client
4.0.11
152
@redhat-cloud-services/patch-client
4.0.4
148
@redhat-cloud-services/hcc-feo-mcp
0.3.1
27
@redhat-cloud-services/hcc-kessel-mcp
0.3.1
27

Runtime Analysis Using Harden-Runner

To observe the malware's behavior at runtime, we installed @redhat-cloud-services/notifications-client@6.1.4 in a controlled GitHub Actions workflow with StepSecurity Harden-Runner enabled in audit mode. Harden-Runner monitors all outbound network connections, process executions, and file writes at the step level, providing full visibility into what happens during npm install.

Harden-Runner process events tab showing Runner.Worker /proc/mem read

The Attack: Entry Point and Execution Chain

Stage 1: Preinstall Hook

Each malicious package declares a preinstall script in package.json that executes node index.js. The index.js file is 4.2 MB, an immediate anomaly for a library package. Execution begins the moment npm install processes the package, before any application code runs and before the developer has any indication something is wrong.

"scripts": {
  "preinstall": "node index.js"
}

Stage 2: ROT-21 Outer Obfuscation

The top-level structure of index.js wraps the entire payload in a try { eval(...) } catch(e) {} block. The inner content is ROT-21 encoded, a Caesar cipher with shift 21 applied to all alphabetic characters. Character codes are stored as a large numeric array reconstructed at runtime via String.fromCharCode. The silent catch block ensures no errors surface to the developer console regardless of environment.

try {
  eval(function(s, n) {
    return s.replace(/[a-zA-Z]/g, function(c) {
      // ROT-21 decode: shift alphabet by n=21
    });
  }([10...numeric char code array...].map(function(c) {
    return String.fromCharCode(c);
  }).join(""), 21))
} catch(e) { /* silent */ }

Decoding the ROT-21 layer reveals a 1.27 MB JavaScript file containing two AES-128-GCM encrypted blobs: _b (the Bun downloader) and _p (the main implant).

Stage 3: Two AES-128-GCM Encrypted Blobs

Both blobs are decrypted at runtime using hardcoded keys and initialization vectors embedded in the Stage 2 output:

Blob
AES-128-GCM Key
IV
Purpose
_b
4c26cf9791bce1bfd4b84eba80ce2754
1241582f04234f7192feacba
Bun runtime downloader
_p
ec514c074caf0ffdce6c66a0e95753d8
1251c3b85365f9b56a956c10
Main payload implant

_b decrypts to a small async JavaScript module that fetches bun-v1.3.13 from the official GitHub releases CDN, extracts it to a randomly-named subdirectory under /tmp/, and registers the binary path in globalThis.getBunPath. The main implant _p is then written to a randomly-named file (/tmp/p<random>.js) and executed via the freshly downloaded Bun binary.

Stage 4: obfuscator.io and a Custom B5 Cipher

The main payload (_p, 634 KB when decrypted) is obfuscated with obfuscator.io using a non-standard base64 alphabet that swaps the case order of letters: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/= (lowercase before uppercase, inverting the standard order). A string table of 2,219 encoded entries undergoes a push/shift rotation of exactly 284 cycles at startup before any string index lookups occur, ensuring that static extraction of string values yields wrong results without simulating the rotation.

On top of the obfuscator.io layer, the most sensitive runtime values, namely C2 domain names, the User-Agent string, and API paths, are encrypted with a secondary bespoke cipher designated B5:

  • Key derivation: PBKDF2(password, salt, 200,000 iterations, 32 bytes output, SHA-256). The password and salt are themselves stored in the obfuscated string table, only accessible after the 284-cycle rotation completes.
  • Per-message key: SHA-256(master_key ∥ iv), where iv is the first 16 bytes of the ciphertext.
  • Decryption: 3 rounds of inverse Fisher-Yates byte-substitution (a per-block, per-round SHA-256-seeded permutation) with XOR feedback chaining between blocks.
  • Ciphertext storage: Each encrypted value is stored as a standard base64 string inside the obfuscator.io string table, which is itself custom-base64 encoded.

The recovered B5 cipher parameters are: password ba2c6ddb3672bdd6a611e6850b4f700b52aed3dab2f1b3d5f8c839d4a157a709, salt 5b26508dc0f1075a7c0b4d8aa464487e. C2 domain decryption using these parameters is in progress; this post will be updated with confirmed domains.

Payload Capabilities

GitHub Actions Runner Memory Extraction

The payload reads from /proc/<pid>/mem to extract live secrets directly from the Runner.Worker process memory. It queries the GitHub Actions runtime API using ACTIONS_RUNTIME_TOKEN to enumerate environment variables and identify which ones are flagged as isSecret: true, the same flag that causes values to be masked in workflow logs. It then targets those specific variables for extraction from process memory. This technique extracts masked secrets that never appear in logs, bypassing the masking mechanism entirely.

// Key patterns identified in deobfuscated payload
/proc/*/mem               // direct process memory read target
Runner.Worker             // process name used to locate runner PID
isSecret                  // GitHub Actions API field to identify masked secrets
ACTIONS_RUNTIME_TOKEN     // used to authenticate against runner API
GITHUB_TOKEN              // primary exfiltration target

Multi-Cloud and Developer Credential Sweep

The payload performs a broad credential sweep across cloud providers, package registries, and developer tools:

Target
Credentials / Files Harvested
GitHub Actions
GITHUB_TOKEN, ACTIONS_RUNTIME_TOKEN, ACTIONS_ID_TOKEN_REQUEST_TOKEN, NPM_TOKEN
AWS
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, ~/.aws/credentials
GCP
Application default credentials, service account key files
Azure
Service principal credentials, AZURE_CLIENT_SECRET, managed identity tokens
HashiCorp Vault
VAULT_TOKEN, VAULT_ADDR
Kubernetes
In-cluster service account token, ~/.kube/config
npm
~/.npmrc (publish tokens)
PyPI
~/.pypirc
SSH
~/.ssh/id_rsa, ~/.ssh/id_ed25519, all private key files
Docker
~/.docker/config.json (registry auth)
GPG
~/.gnupg/
General
.env files throughout the filesystem

Supply-Chain Worm: Autonomous npm Republish

Using harvested npm authentication tokens, the payload attempts to publish new backdoored versions of packages the victim account has access to. Critically, it uses npm's bypass_2fa publish parameter to override two-factor authentication requirements. This capability is available to automation tokens and is used here to make the worm self-propagating even against accounts with 2FA enabled. Each successfully infected machine can autonomously seed the next wave of compromised packages without any further attacker involvement.

GitHub Dead-Drop C2 Channel

Beyond direct HTTPS exfiltration, the payload implements a covert GitHub-based exfiltration channel. Using a harvested GITHUB_TOKEN, it calls the GitHub Contents API to create refs and commits containing base64-encoded stolen data in victim-controlled repositories. This technique routes exfiltrated data through api.github.com, a host that is almost universally permitted in CI network policies, making the exfiltration indistinguishable from normal git operations at the network layer.

Developer Workstation Persistence

The payload installs two persistence mechanisms targeting developer tooling, surviving package removal:

  • Claude Code hijack (~/.claude/settings.json): A SessionStart hook is injected into Claude Code's settings file. This hook executes attacker-controlled code at the beginning of every Claude Code session on the compromised machine, giving the attacker persistent code execution triggered by normal developer workflow.
  • VS Code task injection (.vscode/tasks.json): A folderOpen task trigger is written to workspace task configurations, executing attacker code every time the developer opens a project folder in VS Code.

For StepSecurity Enterprise Customers

Threat Center Alert

StepSecurity has published a threat intel alert in the Threat Center with all relevant links to check if your organization is affected. The alert includes the full attack summary, technical analysis, IOCs, affected versions, and remediation steps, so teams have everything needed to triage and respond immediately. Threat Center alerts are delivered directly into existing SIEM workflows for real-time visibility.

Harden-Runner

Harden-Runner is a purpose-built security agent for CI/CD runners.

It monitors all network events, process executions, file access, and outbound network connections at the step level in GitHub Actions, providing full runtime visibility into what happens during every workflow step, including npm install.

In this campaign, the malicious payload attempts to read the Runner.Worker process memory to extract plaintext secrets, including GITHUB_TOKEN and all secrets injected into the workflow, directly from the runner's address space without ever writing them to disk or making a suspicious network connection.

Harden-Runner detects this and immediately initiates lockdown mode, terminating the malicious process before the memory read can complete and preventing any secrets from being extracted. The workflow run is halted and a suspicious process event is recorded in the runtime trace.

Link to the github run : https://app.stepsecurity.io/github/actions-security-demo/compromised-packages/actions/runs/26751375948

Secure Registry

StepSecurity Secure Registry provides each enterprise customer with a dedicated, policy-enforced npm registry that sits between your existing package manager (such as JFrog Artifactory) and the public npm registry. Instead of fetching packages directly from registry.npmjs.org, your infrastructure routes requests through your StepSecurity registry, which applies configurable security policies before serving any package.

The primary defense here is the cooldown period. Newly published package versions are held for a configurable window before being served to any developer machine or CI/CD pipeline. When the compromised AntV packages were published to npm, Secure Registry customers were never exposed. Their registries continued serving the last known safe versions while the cooldown clock ran, giving the community and StepSecurity's AI Package Analyst time to flag and permanently block the malicious releases.

Detect Compromised Developer Machines

Supply chain attacks like this one do not stop at the CI/CD pipeline. The malicious payload harvests credentials, SSH keys, cloud tokens, cryptocurrency wallets, and AI tool configurations from the local environment. Every developer who ran npm install with a compromised version outside of CI is a potential point of compromise.

StepSecurity Dev Machine Guard gives security teams real-time visibility into npm packages installed across every enrolled developer device. When a malicious package is identified, teams can immediately search by package name and version to discover all impacted machines.

npm Package Cooldown Check

Newly published npm packages are temporarily blocked during a configurable cooldown window. When a PR introduces or updates to a recently published version, the check automatically fails. Since most malicious packages are identified within hours, this creates a crucial safety buffer. In this case, the compromised AntV versions were published in rapid succession on May 19, so any PR updating to an affected version during the cooldown period would have been blocked automatically.

npm Package Compromised Updates Check

StepSecurity maintains a real-time database of known malicious and high-risk npm packages, updated continuously, often before official CVEs are filed. If a PR attempts to introduce a compromised package, the check fails and the merge is blocked. All compromised versions from this campaign were added to this database within minutes of detection.

npm Package Search

Search across all PRs in all repositories across your organization to find where a specific package was introduced. When a compromised package is discovered, instantly understand the blast radius: which repos, which PRs, and which teams are affected. This works across pull requests, default branches, and dev machines.

AI Package Analyst

AI Package Analyst continuously monitors the npm registry for suspicious releases in real time, scoring packages for supply chain risk before you install them. In this case, the compromised AntV versions were flagged within minutes of publication, giving teams time to investigate, confirm malicious intent, and act before the packages accumulated significant installs. The payload size anomaly, injected index.js at the package root, and the optionalDependencies reference to a GitHub commit were all surfaced as high-confidence supply chain indicators.

This post will be updated as technical analysis of the remaining packages progresses, including full payload deobfuscation, recovery of encrypted C2 domains, and any additional indicators of compromise identified.

Blog

Explore Related Posts